diff --git a/.github/workflows/apt-deps.txt b/.github/workflows/apt-deps.txt index dba3511bf4c..e1ef5d64d34 100644 --- a/.github/workflows/apt-deps.txt +++ b/.github/workflows/apt-deps.txt @@ -1 +1 @@ -libusb-dev libusb-1.0-0-dev libsdl2-dev libsdl2-net-dev libpng-dev libglew-dev nlohmann-json3-dev libtinyxml2-dev libspdlog-dev ninja-build libogg-dev libopus-dev opus-tools libopusfile-dev libvorbis-dev libespeak-ng-dev \ No newline at end of file +libusb-dev libusb-1.0-0-dev libsdl2-dev libsdl2-net-dev libpng-dev libglew-dev nlohmann-json3-dev libtinyxml2-dev libspdlog-dev ninja-build libogg-dev libopus-dev opus-tools libopusfile-dev libvorbis-dev libespeak-ng-dev libwebsocketpp-dev diff --git a/.github/workflows/generate-builds.yml b/.github/workflows/generate-builds.yml index c1c00ad088f..afcb5a2901d 100644 --- a/.github/workflows/generate-builds.yml +++ b/.github/workflows/generate-builds.yml @@ -74,81 +74,7 @@ jobs: path: soh.o2r retention-days: 3 - build-macos: - needs: generate-soh-otr - runs-on: macos-14 - steps: - - name: Git Checkout - uses: actions/checkout@v4 - with: - submodules: true - - name: Configure ccache - uses: hendrikmuhs/ccache-action@v1.2 - with: - create-symlink: true - save: ${{ github.ref_name == github.event.repository.default_branch }} - key: ${{ runner.os }}-14-ccache-${{ github.ref }}-${{ github.sha }} - restore-keys: | - ${{ runner.os }}-14-ccache-${{ github.ref }} - ${{ runner.os }}-14-ccache - # Needed to apply sudo for macports cache restore - - name: Install gtar wrapper - run: | - sudo mv /opt/homebrew/bin/gtar /opt/homebrew/bin/gtar.orig - sudo cp .github/workflows/gtar /opt/homebrew/bin/gtar - sudo chmod +x /opt/homebrew/bin/gtar - - name: Restore Cached MacPorts - id: restore-cache-macports - uses: actions/cache/restore@v4 - with: - key: ${{ runner.os }}-14-macports-${{ hashFiles('.github/workflows/macports-deps.txt') }}-${{ github.sha }} - restore-keys: | - ${{ runner.os }}-14-macports-${{ hashFiles('.github/workflows/macports-deps.txt') }}- - ${{ runner.os }}-14-macports- - path: /opt/local/ - # Updated PATH applies to the next step and onwards - - name: Install MacPorts (if necessary) - run: | - if command -v /opt/local/bin/port 2>&1 >/dev/null; then - echo "MacPorts already installed" - else - echo "Installing MacPorts" - wget https://github.com/macports/macports-base/releases/download/v2.11.5/MacPorts-2.11.5-14-Sonoma.pkg - sudo installer -pkg ./MacPorts-2.11.5-14-Sonoma.pkg -target / - fi - echo "/opt/local/bin:/opt/local/sbin" >> "$GITHUB_PATH" - - name: Install dependencies - run: | - brew uninstall --ignore-dependencies libpng - sudo port install $(cat .github/workflows/macports-deps.txt) - brew install ninja - - name: Download soh.o2r - uses: actions/download-artifact@v4 - with: - name: soh.o2r - path: build-cmake/soh - - name: Build SoH - run: | - export PATH="/usr/lib/ccache:/opt/homebrew/opt/ccache/libexec:/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" -DBUILD_REMOTE_CONTROL=1 - cmake --build build-cmake --config Release --parallel 10 - (cd build-cmake && cpack) - - mv _packages/*.dmg SoH.dmg - mv README.md readme.txt - - name: Upload build - uses: actions/upload-artifact@v4 - with: - name: soh-mac - path: | - SoH.dmg - readme.txt - - name: Save Cache MacPorts - if: ${{ github.ref_name == github.event.repository.default_branch }} - uses: actions/cache/save@v4 - with: - key: ${{ steps.restore-cache-macports.outputs.cache-primary-key }} - path: /opt/local/ + # macOS job removed build-linux: needs: generate-soh-otr @@ -233,6 +159,23 @@ jobs: make sudo make install sudo cp -av /usr/local/lib/libzip* /lib/x86_64-linux-gnu/ + - name: Install valijson + run: | + export PATH="/usr/lib/ccache:/usr/local/opt/ccache/libexec:$PATH" + if [ ! -d "deps/valijson-1.0.6" ]; then + wget https://github.com/tristanpenman/valijson/archive/refs/tags/v1.0.6.tar.gz + tar -xzf v1.0.6.tar.gz -C deps + fi + cd deps/valijson-1.0.6 + mkdir -p build + cd build + cmake .. + sudo cmake --install . + - name: Patch websocketpp + run: | + sudo sed -i 's/endpoint(/endpoint(/g' /usr/include/websocketpp/endpoint.hpp + sudo sed -i 's/basic(/basic(/g' /usr/include/websocketpp/logger/basic.hpp + sudo sed -i 's/server(/server(/g' /usr/include/websocketpp/roles/server_endpoint.hpp - name: Download soh.o2r uses: actions/download-artifact@v4 with: @@ -250,6 +193,13 @@ jobs: env: CC: gcc-12 CXX: g++-12 + + # NEW: include oot_soh.apworld in Linux artifact + - name: Add oot_soh.apworld to Linux artifact + run: | + mkdir -p Archipelago/custom_worlds + cp archipelago/oot_soh.apworld Archipelago/custom_worlds/oot_soh.apworld + - name: Upload build uses: actions/upload-artifact@v4 with: @@ -257,6 +207,7 @@ jobs: path: | soh.appimage readme.txt + Archipelago - name: Save Cache deps folder if: ${{ github.ref_name == github.event.repository.default_branch }} uses: actions/cache/save@v4 @@ -316,6 +267,13 @@ jobs: mv _packages/*.zip _packages/soh-windows.zip - name: Unzip package run: Expand-Archive -Path _packages/soh-windows.zip -DestinationPath soh-windows + + # NEW: include oot_soh.apworld in Windows artifact + - name: Add oot_soh.apworld to Windows artifact + run: | + New-Item -ItemType Directory -Force -Path "soh-windows/Archipelago/custom_worlds" | Out-Null + Copy-Item "archipelago/oot_soh.apworld" "soh-windows/Archipelago/custom_worlds/oot_soh.apworld" -Force + - name: Upload build uses: actions/upload-artifact@v4 with: diff --git a/.github/workflows/macports-deps.txt b/.github/workflows/macports-deps.txt index fd97d0c3066..8b8e3398865 100644 --- a/.github/workflows/macports-deps.txt +++ b/.github/workflows/macports-deps.txt @@ -1 +1 @@ -libsdl2 +universal libsdl2_net +universal libpng +universal glew +universal libzip +universal nlohmann-json +universal tinyxml2 +universal libogg +universal libopus +universal opusfile +universal libvorbis +universal \ No newline at end of file +libsdl2 +universal libsdl2_net +universal libpng +universal glew +universal libzip +universal nlohmann-json +universal tinyxml2 +universal libogg +universal libopus +universal opusfile +universal libvorbis +universal openssl +universal websocketpp +universal diff --git a/.gitmodules b/.gitmodules index 7098d9d0822..95537753093 100644 --- a/.gitmodules +++ b/.gitmodules @@ -7,3 +7,9 @@ [submodule "OTRExporter"] path = OTRExporter url = https://github.com/harbourmasters/OTRExporter +[submodule "subprojects/wswrap"] + path = subprojects/wswrap + url = https://github.com/black-sliver/wswrap.git +[submodule "subprojects/apclientpp"] + path = subprojects/apclientpp + url = https://github.com/black-sliver/apclientpp.git diff --git a/CMakeLists.txt b/CMakeLists.txt index c6c6788b15d..e2348f81d43 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -38,7 +38,7 @@ math(EXPR PATCH_INDEX "${PROJECT_VERSION_PATCH}") # Use the patch number to select the correct word list(GET NATO_PHONETIC_ALPHABET ${PATCH_INDEX} PROJECT_PATCH_WORD) -set(PROJECT_BUILD_NAME "Copper ${PROJECT_PATCH_WORD}" CACHE STRING "" FORCE) +set(PROJECT_BUILD_NAME "TheHeckinBuild" CACHE STRING "" FORCE) set(PROJECT_TEAM "github.com/harbourmasters" CACHE STRING "" FORCE) execute_process( @@ -86,7 +86,7 @@ if (CMAKE_SYSTEM_NAME STREQUAL "Windows") set(VCPKG_TARGET_TRIPLET x64-windows-static) vcpkg_bootstrap() - vcpkg_install_packages(zlib bzip2 libzip libpng sdl2 sdl2-net glew glfw3 nlohmann-json tinyxml2 spdlog libogg libvorbis opus opusfile) + vcpkg_install_packages(zlib bzip2 libzip libpng sdl2 sdl2-net glew glfw3 nlohmann-json tinyxml2 spdlog libogg libvorbis opus opusfile openssl valijson) if (CMAKE_C_COMPILER_LAUNCHER MATCHES "ccache|sccache") set(CMAKE_MSVC_DEBUG_INFORMATION_FORMAT Embedded) endif() diff --git a/RogueLike.md b/RogueLike.md new file mode 100644 index 00000000000..7a3ea13b79c --- /dev/null +++ b/RogueLike.md @@ -0,0 +1,38 @@ +# Game Design Document +The gist of this idea is you play through the game similar to a randomizer, fulfilling the objectives to reach and beat Ganon, but instead of getting items from checks, they are granted with a leveling/reward system. Among other things like buffs and debuffs. + +## Starting out UX +When beginning a game, you will have multiple randomly rolled selections to make, including a starting location, starting item, starting song + +## Gameplay +As you progress through the game, you will be rewarded XP for doing various actions throughout the world. When you have enough XP you will level up and you will be presented with a selection of buffs, items, or other rewards to choose from to enhance your character for the rest of the run. Actions include interacting with things, breaking things, killing enemies, talking with NPCs and completing their quests, etc. + +TODO: +- Health upgrades not implemented +- Defense doesn't do anything yet (maybe damage mitigation?) +- Speed doesn't do anything yet (will effect movement speed of bunny hood) +- Quests system in early stages, only two quests currently +- Don't have a global enemy spawning mechanism yet, just relying on existing enemy spawn points +- Not all checks have been replaced with XP rewards yet +- Not sure what to do about ammo yet +- Not sure what to do about shops yet +- Weighted list of XP amounts to grant for every check and enemy. For instance you shouldn't get that much XP for opening chests in Mido's house, but doing frog song minigame is huge etc + +## Difficulty Scaling +The main thing that will scale is enemy health & damage to link. Difficulty increases in levels, it will increase each time you kill a major boss, and there will also be a timer that eventually increases your difficulty based on time spent not gaining any experience. + +TODO: +- Damage to link scaling not implemented yet +- Defeating bosses to increase difficulty not implemented yet +- Hyper enemies / enemy size scaling could play into this? + +## Permanent progression +As you continue playing, the idea is you would very slowly gain a currency to grant you very small bonuses that will benefit you in every following run. Starting health is an example of a potential purchase-able thing, or starting with ocarina or bunny hood. It's likely that this currency would be earned from killing bosses and completing quests + +TODO: +- Permanent progression system not implemented yet + +## Ideas: +- Lock the player in certain areas to spawn specified waves of enemies before the player can proceed? i.e Hyrule Field or places without a Clear Room +- Utilize the trick from MM to hide/unhide Inventory Items based on what is earned? +- Ammo could have a timed regen based on upgrades? For instance you can only hold 1 stick, and if you break it, it regens after 5 minutes, unless you get an upgrade to hold 2 sticks, then it regens after 4 minutes, etc \ No newline at end of file diff --git a/archipelago/oot_soh.apworld b/archipelago/oot_soh.apworld new file mode 100644 index 00000000000..a11b7a5dcbc Binary files /dev/null and b/archipelago/oot_soh.apworld differ diff --git a/soh/CMakeLists.txt b/soh/CMakeLists.txt index b0630e84163..102f3f583f0 100644 --- a/soh/CMakeLists.txt +++ b/soh/CMakeLists.txt @@ -1,4 +1,5 @@ cmake_minimum_required(VERSION 3.26.0 FATAL_ERROR) +include(FetchContent) set(CMAKE_SYSTEM_VERSION 10.0 CACHE STRING "" FORCE) @@ -282,11 +283,68 @@ if (CMAKE_SYSTEM_NAME STREQUAL "Windows") endif() set_target_properties(${PROJECT_NAME} PROPERTIES MSVC_RUNTIME_LIBRARY ${MSVC_RUNTIME_LIBRARY_STR}) endif() + +include(FetchContent) + ################################################################################ -# Find/download Dr Libs (For custom audio) +# apclientpp + dependencies ################################################################################ -include(FetchContent) +add_compile_definitions(ASIO_STANDALONE) + +if(BUILD_REMOTE_CONTROL) + if(CMAKE_SYSTEM_NAME STREQUAL "Darwin") + FetchContent_Declare( + valijson + GIT_REPOSITORY https://github.com/tristanpenman/valijson.git + GIT_TAG v1.0.6 + ) + FetchContent_MakeAvailable(valijson) + target_include_directories(${PROJECT_NAME} PRIVATE ${valijson_SOURCE_DIR}/include) + else() + find_package(valijson REQUIRED) + endif() + + FetchContent_Declare( + asio + GIT_REPOSITORY https://github.com/chriskohlhoff/asio.git + GIT_TAG asio-1-30-2 + ) + FetchContent_MakeAvailable(asio) + target_include_directories(${PROJECT_NAME} PRIVATE ${asio_SOURCE_DIR}/asio/include) + + # websocketpp has been temporarily removed from vcpkg, see + # https://github.com/microsoft/vcpkg/pull/42678#issuecomment-2914451436 + if(CMAKE_SYSTEM_NAME STREQUAL "Windows") + FetchContent_Declare( + websocketpp + GIT_REPOSITORY https://github.com/zaphoyd/websocketpp.git + GIT_TAG 0.8.2 + ) + FetchContent_Populate(websocketpp) + target_include_directories(${PROJECT_NAME} PRIVATE ${websocketpp_SOURCE_DIR}) + else() + find_package(websocketpp REQUIRED) + endif() + + FetchContent_Declare( + wswrap + GIT_REPOSITORY https://github.com/black-sliver/wswrap.git + GIT_TAG 47438193ec50427ee28aadf294ba57baefd9f3f1 + ) + FetchContent_MakeAvailable(wswrap) + target_include_directories(${PROJECT_NAME} PRIVATE ${wswrap_SOURCE_DIR}/include) + + FetchContent_Declare( + apclientpp + GIT_REPOSITORY https://github.com/black-sliver/apclientpp.git + GIT_TAG 65638b7479f6894eda172e603cffa79762c0ddc1 + ) + FetchContent_MakeAvailable(apclientpp) + target_include_directories(${PROJECT_NAME} PRIVATE ${apclientpp_SOURCE_DIR}) +endif() +# Find/download Dr Libs (For custom audio) +################################################################################ FetchContent_Declare( dr_libs GIT_REPOSITORY https://github.com/mackron/dr_libs.git @@ -294,6 +352,9 @@ FetchContent_Declare( ) FetchContent_MakeAvailable(dr_libs) +################################################################################ +# Compile definitions +################################################################################ find_package(SDL2) set(SDL2-INCLUDE ${SDL2_INCLUDE_DIRS}) @@ -301,11 +362,44 @@ if (BUILD_REMOTE_CONTROL) find_package(SDL2_net) if(NOT SDL2_net_FOUND) - message(STATUS "SDL2_net not found (it's possible the version installed is too old). Disabling BUILD_REMOTE_CONTROL.") + # todo: make archi optional so this can build with BUILD_REMOTE_CONTROL off + message(FATAL_ERROR "SDL2_net not found (it's possible the version installed is too old).") set(BUILD_REMOTE_CONTROL 0) else() set(SDL2-NET-INCLUDE ${SDL_NET_INCLUDE_DIRS}) endif() + + set(OPENSSL_USE_STATIC_LIBS ON) + + if(CMAKE_SYSTEM_NAME STREQUAL "Linux") + file(STRINGS /etc/os-release distro REGEX "^NAME=") + string(REGEX REPLACE "NAME=\"(.*)\"" "\\1" distro "${distro}") + + if(${distro} MATCHES "Fedora Linux") + set(OPENSSL_USE_STATIC_LIBS OFF) + endif() + endif() + + find_package(OpenSSL) + if(NOT OPENSSL_FOUND) + # todo: make archi optional so this can build with BUILD_REMOTE_CONTROL off + message(FATAL_ERROR "OpenSSL not found (it's possible the version installed is too old).") + set(BUILD_REMOTE_CONTROL 0) + endif() + + FetchContent_Declare( + sslCertStore + URL https://curl.se/ca/cacert.pem + DOWNLOAD_DIR ${CMAKE_CURRENT_BINARY_DIR}/networking + DOWNLOAD_NO_EXTRACT TRUE + ) + + FetchContent_MakeAvailable( + sslCertStore + ) + + install(FILES ${CMAKE_CURRENT_BINARY_DIR}/networking/cacert.pem DESTINATION ./networking COMPONENT ship) + endif() if (ESPEAK) @@ -410,6 +504,7 @@ endif() # Compile and link options ################################################################################ if(MSVC) + target_compile_options(${PROJECT_NAME} PUBLIC "/Zc:__cplusplus") if("${CMAKE_VS_PLATFORM_NAME}" STREQUAL "x64") target_compile_options(${PROJECT_NAME} PRIVATE $<$: @@ -643,6 +738,8 @@ if (CMAKE_SYSTEM_NAME STREQUAL "Windows") "SDL2::SDL2;" "SDL2::SDL2main;" "$<$:SDL2_net::SDL2_net-static>" + "$<$:OpenSSL::SSL>" + "$<$:OpenSSL::Crypto>" "glfw;" "winmm;" "imm32;" @@ -710,6 +807,8 @@ else() "Opus::opus" "Opusfile::Opusfile" "$<$:SDL2_net::SDL2_net>" + "$<$:OpenSSL::SSL>" + "$<$:OpenSSL::Crypto>" ${CMAKE_DL_LIBS} Threads::Threads ) diff --git a/soh/assets/custom/objects/custom_snowball/LightNoise.rgba32 b/soh/assets/custom/objects/custom_snowball/LightNoise.rgba32 new file mode 100644 index 00000000000..73bcf2a59c1 Binary files /dev/null and b/soh/assets/custom/objects/custom_snowball/LightNoise.rgba32 differ diff --git a/soh/assets/custom/objects/custom_snowball/mat_snowball_snow b/soh/assets/custom/objects/custom_snowball/mat_snowball_snow new file mode 100644 index 00000000000..49b24d2b556 --- /dev/null +++ b/soh/assets/custom/objects/custom_snowball/mat_snowball_snow @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/custom_snowball/model.xml b/soh/assets/custom/objects/custom_snowball/model.xml new file mode 100644 index 00000000000..8e2e4aa6ebd --- /dev/null +++ b/soh/assets/custom/objects/custom_snowball/model.xml @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/soh/assets/custom/objects/custom_snowball/snowball b/soh/assets/custom/objects/custom_snowball/snowball new file mode 100644 index 00000000000..ca9fb8e40d1 --- /dev/null +++ b/soh/assets/custom/objects/custom_snowball/snowball @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/soh/assets/custom/objects/custom_snowball/snowball_tri_0 b/soh/assets/custom/objects/custom_snowball/snowball_tri_0 new file mode 100644 index 00000000000..7c4fb871d81 --- /dev/null +++ b/soh/assets/custom/objects/custom_snowball/snowball_tri_0 @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/custom_snowball/snowball_vtx_0 b/soh/assets/custom/objects/custom_snowball/snowball_vtx_0 new file mode 100644 index 00000000000..69080c08990 --- /dev/null +++ b/soh/assets/custom/objects/custom_snowball/snowball_vtx_0 @@ -0,0 +1,92 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/custom_snowball/snowball_vtx_cull b/soh/assets/custom/objects/custom_snowball/snowball_vtx_cull new file mode 100644 index 00000000000..8e2e4aa6ebd --- /dev/null +++ b/soh/assets/custom/objects/custom_snowball/snowball_vtx_cull @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_archipelago_item/gArchipelagoItemDL b/soh/assets/custom/objects/object_archipelago_item/gArchipelagoItemDL new file mode 100644 index 00000000000..33e22a85fa9 --- /dev/null +++ b/soh/assets/custom/objects/object_archipelago_item/gArchipelagoItemDL @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_archipelago_item/gArchipelagoItemDL_tri_0 b/soh/assets/custom/objects/object_archipelago_item/gArchipelagoItemDL_tri_0 new file mode 100644 index 00000000000..42e91141193 --- /dev/null +++ b/soh/assets/custom/objects/object_archipelago_item/gArchipelagoItemDL_tri_0 @@ -0,0 +1,56 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_archipelago_item/gArchipelagoItemDL_tri_1 b/soh/assets/custom/objects/object_archipelago_item/gArchipelagoItemDL_tri_1 new file mode 100644 index 00000000000..259b27c4e4d --- /dev/null +++ b/soh/assets/custom/objects/object_archipelago_item/gArchipelagoItemDL_tri_1 @@ -0,0 +1,57 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_archipelago_item/gArchipelagoItemDL_tri_2 b/soh/assets/custom/objects/object_archipelago_item/gArchipelagoItemDL_tri_2 new file mode 100644 index 00000000000..dc680a34a13 --- /dev/null +++ b/soh/assets/custom/objects/object_archipelago_item/gArchipelagoItemDL_tri_2 @@ -0,0 +1,57 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_archipelago_item/gArchipelagoItemDL_tri_3 b/soh/assets/custom/objects/object_archipelago_item/gArchipelagoItemDL_tri_3 new file mode 100644 index 00000000000..a134560fb5d --- /dev/null +++ b/soh/assets/custom/objects/object_archipelago_item/gArchipelagoItemDL_tri_3 @@ -0,0 +1,57 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_archipelago_item/gArchipelagoItemDL_tri_4 b/soh/assets/custom/objects/object_archipelago_item/gArchipelagoItemDL_tri_4 new file mode 100644 index 00000000000..65134138f8f --- /dev/null +++ b/soh/assets/custom/objects/object_archipelago_item/gArchipelagoItemDL_tri_4 @@ -0,0 +1,57 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_archipelago_item/gArchipelagoItemDL_tri_5 b/soh/assets/custom/objects/object_archipelago_item/gArchipelagoItemDL_tri_5 new file mode 100644 index 00000000000..3b458b7368d --- /dev/null +++ b/soh/assets/custom/objects/object_archipelago_item/gArchipelagoItemDL_tri_5 @@ -0,0 +1,56 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_archipelago_item/gArchipelagoItemDL_tri_6 b/soh/assets/custom/objects/object_archipelago_item/gArchipelagoItemDL_tri_6 new file mode 100644 index 00000000000..b21f2c4b739 --- /dev/null +++ b/soh/assets/custom/objects/object_archipelago_item/gArchipelagoItemDL_tri_6 @@ -0,0 +1,56 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_archipelago_item/gArchipelagoItemDL_vtx_0 b/soh/assets/custom/objects/object_archipelago_item/gArchipelagoItemDL_vtx_0 new file mode 100644 index 00000000000..9f4483ec634 --- /dev/null +++ b/soh/assets/custom/objects/object_archipelago_item/gArchipelagoItemDL_vtx_0 @@ -0,0 +1,64 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_archipelago_item/gArchipelagoItemDL_vtx_1 b/soh/assets/custom/objects/object_archipelago_item/gArchipelagoItemDL_vtx_1 new file mode 100644 index 00000000000..9339e42f05c --- /dev/null +++ b/soh/assets/custom/objects/object_archipelago_item/gArchipelagoItemDL_vtx_1 @@ -0,0 +1,77 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_archipelago_item/gArchipelagoItemDL_vtx_2 b/soh/assets/custom/objects/object_archipelago_item/gArchipelagoItemDL_vtx_2 new file mode 100644 index 00000000000..e1122533a0b --- /dev/null +++ b/soh/assets/custom/objects/object_archipelago_item/gArchipelagoItemDL_vtx_2 @@ -0,0 +1,70 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_archipelago_item/gArchipelagoItemDL_vtx_3 b/soh/assets/custom/objects/object_archipelago_item/gArchipelagoItemDL_vtx_3 new file mode 100644 index 00000000000..29c025d0fdb --- /dev/null +++ b/soh/assets/custom/objects/object_archipelago_item/gArchipelagoItemDL_vtx_3 @@ -0,0 +1,68 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_archipelago_item/gArchipelagoItemDL_vtx_4 b/soh/assets/custom/objects/object_archipelago_item/gArchipelagoItemDL_vtx_4 new file mode 100644 index 00000000000..c3c88918b35 --- /dev/null +++ b/soh/assets/custom/objects/object_archipelago_item/gArchipelagoItemDL_vtx_4 @@ -0,0 +1,69 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_archipelago_item/gArchipelagoItemDL_vtx_5 b/soh/assets/custom/objects/object_archipelago_item/gArchipelagoItemDL_vtx_5 new file mode 100644 index 00000000000..70117ba12dc --- /dev/null +++ b/soh/assets/custom/objects/object_archipelago_item/gArchipelagoItemDL_vtx_5 @@ -0,0 +1,64 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_archipelago_item/gArchipelagoItemDL_vtx_6 b/soh/assets/custom/objects/object_archipelago_item/gArchipelagoItemDL_vtx_6 new file mode 100644 index 00000000000..70117ba12dc --- /dev/null +++ b/soh/assets/custom/objects/object_archipelago_item/gArchipelagoItemDL_vtx_6 @@ -0,0 +1,64 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_archipelago_item/gArchipelagoJunkDL b/soh/assets/custom/objects/object_archipelago_item/gArchipelagoJunkDL new file mode 100644 index 00000000000..4beef5eb34b --- /dev/null +++ b/soh/assets/custom/objects/object_archipelago_item/gArchipelagoJunkDL @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_archipelago_item/gArchipelagoJunkDL_tri_0 b/soh/assets/custom/objects/object_archipelago_item/gArchipelagoJunkDL_tri_0 new file mode 100644 index 00000000000..f2137f1354c --- /dev/null +++ b/soh/assets/custom/objects/object_archipelago_item/gArchipelagoJunkDL_tri_0 @@ -0,0 +1,56 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_archipelago_item/gArchipelagoJunkDL_tri_1 b/soh/assets/custom/objects/object_archipelago_item/gArchipelagoJunkDL_tri_1 new file mode 100644 index 00000000000..ae92327e401 --- /dev/null +++ b/soh/assets/custom/objects/object_archipelago_item/gArchipelagoJunkDL_tri_1 @@ -0,0 +1,57 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_archipelago_item/gArchipelagoJunkDL_tri_2 b/soh/assets/custom/objects/object_archipelago_item/gArchipelagoJunkDL_tri_2 new file mode 100644 index 00000000000..bbbc9643981 --- /dev/null +++ b/soh/assets/custom/objects/object_archipelago_item/gArchipelagoJunkDL_tri_2 @@ -0,0 +1,57 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_archipelago_item/gArchipelagoJunkDL_tri_3 b/soh/assets/custom/objects/object_archipelago_item/gArchipelagoJunkDL_tri_3 new file mode 100644 index 00000000000..6d11e6b1cd3 --- /dev/null +++ b/soh/assets/custom/objects/object_archipelago_item/gArchipelagoJunkDL_tri_3 @@ -0,0 +1,57 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_archipelago_item/gArchipelagoJunkDL_tri_4 b/soh/assets/custom/objects/object_archipelago_item/gArchipelagoJunkDL_tri_4 new file mode 100644 index 00000000000..827df8c0de7 --- /dev/null +++ b/soh/assets/custom/objects/object_archipelago_item/gArchipelagoJunkDL_tri_4 @@ -0,0 +1,57 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_archipelago_item/gArchipelagoJunkDL_tri_5 b/soh/assets/custom/objects/object_archipelago_item/gArchipelagoJunkDL_tri_5 new file mode 100644 index 00000000000..6714bf7a47f --- /dev/null +++ b/soh/assets/custom/objects/object_archipelago_item/gArchipelagoJunkDL_tri_5 @@ -0,0 +1,56 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_archipelago_item/gArchipelagoJunkDL_tri_6 b/soh/assets/custom/objects/object_archipelago_item/gArchipelagoJunkDL_tri_6 new file mode 100644 index 00000000000..3980be4836b --- /dev/null +++ b/soh/assets/custom/objects/object_archipelago_item/gArchipelagoJunkDL_tri_6 @@ -0,0 +1,56 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_archipelago_item/gArchipelagoJunkDL_vtx_0 b/soh/assets/custom/objects/object_archipelago_item/gArchipelagoJunkDL_vtx_0 new file mode 100644 index 00000000000..9f4483ec634 --- /dev/null +++ b/soh/assets/custom/objects/object_archipelago_item/gArchipelagoJunkDL_vtx_0 @@ -0,0 +1,64 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_archipelago_item/gArchipelagoJunkDL_vtx_1 b/soh/assets/custom/objects/object_archipelago_item/gArchipelagoJunkDL_vtx_1 new file mode 100644 index 00000000000..9339e42f05c --- /dev/null +++ b/soh/assets/custom/objects/object_archipelago_item/gArchipelagoJunkDL_vtx_1 @@ -0,0 +1,77 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_archipelago_item/gArchipelagoJunkDL_vtx_2 b/soh/assets/custom/objects/object_archipelago_item/gArchipelagoJunkDL_vtx_2 new file mode 100644 index 00000000000..e1122533a0b --- /dev/null +++ b/soh/assets/custom/objects/object_archipelago_item/gArchipelagoJunkDL_vtx_2 @@ -0,0 +1,70 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_archipelago_item/gArchipelagoJunkDL_vtx_3 b/soh/assets/custom/objects/object_archipelago_item/gArchipelagoJunkDL_vtx_3 new file mode 100644 index 00000000000..29c025d0fdb --- /dev/null +++ b/soh/assets/custom/objects/object_archipelago_item/gArchipelagoJunkDL_vtx_3 @@ -0,0 +1,68 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_archipelago_item/gArchipelagoJunkDL_vtx_4 b/soh/assets/custom/objects/object_archipelago_item/gArchipelagoJunkDL_vtx_4 new file mode 100644 index 00000000000..c3c88918b35 --- /dev/null +++ b/soh/assets/custom/objects/object_archipelago_item/gArchipelagoJunkDL_vtx_4 @@ -0,0 +1,69 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_archipelago_item/gArchipelagoJunkDL_vtx_5 b/soh/assets/custom/objects/object_archipelago_item/gArchipelagoJunkDL_vtx_5 new file mode 100644 index 00000000000..70117ba12dc --- /dev/null +++ b/soh/assets/custom/objects/object_archipelago_item/gArchipelagoJunkDL_vtx_5 @@ -0,0 +1,64 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_archipelago_item/gArchipelagoJunkDL_vtx_6 b/soh/assets/custom/objects/object_archipelago_item/gArchipelagoJunkDL_vtx_6 new file mode 100644 index 00000000000..70117ba12dc --- /dev/null +++ b/soh/assets/custom/objects/object_archipelago_item/gArchipelagoJunkDL_vtx_6 @@ -0,0 +1,64 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_archipelago_item/gArchipelagoProgressiveDL b/soh/assets/custom/objects/object_archipelago_item/gArchipelagoProgressiveDL new file mode 100644 index 00000000000..ed07b28c9c7 --- /dev/null +++ b/soh/assets/custom/objects/object_archipelago_item/gArchipelagoProgressiveDL @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_archipelago_item/gArchipelagoProgressiveDL_tri_0 b/soh/assets/custom/objects/object_archipelago_item/gArchipelagoProgressiveDL_tri_0 new file mode 100644 index 00000000000..1a679a4634a --- /dev/null +++ b/soh/assets/custom/objects/object_archipelago_item/gArchipelagoProgressiveDL_tri_0 @@ -0,0 +1,56 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_archipelago_item/gArchipelagoProgressiveDL_tri_1 b/soh/assets/custom/objects/object_archipelago_item/gArchipelagoProgressiveDL_tri_1 new file mode 100644 index 00000000000..f49d299efda --- /dev/null +++ b/soh/assets/custom/objects/object_archipelago_item/gArchipelagoProgressiveDL_tri_1 @@ -0,0 +1,57 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_archipelago_item/gArchipelagoProgressiveDL_tri_2 b/soh/assets/custom/objects/object_archipelago_item/gArchipelagoProgressiveDL_tri_2 new file mode 100644 index 00000000000..ea82a9347bb --- /dev/null +++ b/soh/assets/custom/objects/object_archipelago_item/gArchipelagoProgressiveDL_tri_2 @@ -0,0 +1,57 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_archipelago_item/gArchipelagoProgressiveDL_tri_3 b/soh/assets/custom/objects/object_archipelago_item/gArchipelagoProgressiveDL_tri_3 new file mode 100644 index 00000000000..8cfca6524a4 --- /dev/null +++ b/soh/assets/custom/objects/object_archipelago_item/gArchipelagoProgressiveDL_tri_3 @@ -0,0 +1,57 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_archipelago_item/gArchipelagoProgressiveDL_tri_4 b/soh/assets/custom/objects/object_archipelago_item/gArchipelagoProgressiveDL_tri_4 new file mode 100644 index 00000000000..639c5aea4b2 --- /dev/null +++ b/soh/assets/custom/objects/object_archipelago_item/gArchipelagoProgressiveDL_tri_4 @@ -0,0 +1,57 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_archipelago_item/gArchipelagoProgressiveDL_tri_5 b/soh/assets/custom/objects/object_archipelago_item/gArchipelagoProgressiveDL_tri_5 new file mode 100644 index 00000000000..99a4852cbef --- /dev/null +++ b/soh/assets/custom/objects/object_archipelago_item/gArchipelagoProgressiveDL_tri_5 @@ -0,0 +1,56 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_archipelago_item/gArchipelagoProgressiveDL_tri_6 b/soh/assets/custom/objects/object_archipelago_item/gArchipelagoProgressiveDL_tri_6 new file mode 100644 index 00000000000..e7ab8546cf4 --- /dev/null +++ b/soh/assets/custom/objects/object_archipelago_item/gArchipelagoProgressiveDL_tri_6 @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_archipelago_item/gArchipelagoProgressiveDL_tri_7 b/soh/assets/custom/objects/object_archipelago_item/gArchipelagoProgressiveDL_tri_7 new file mode 100644 index 00000000000..0f50ed430cd --- /dev/null +++ b/soh/assets/custom/objects/object_archipelago_item/gArchipelagoProgressiveDL_tri_7 @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_archipelago_item/gArchipelagoProgressiveDL_vtx_0 b/soh/assets/custom/objects/object_archipelago_item/gArchipelagoProgressiveDL_vtx_0 new file mode 100644 index 00000000000..9f4483ec634 --- /dev/null +++ b/soh/assets/custom/objects/object_archipelago_item/gArchipelagoProgressiveDL_vtx_0 @@ -0,0 +1,64 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_archipelago_item/gArchipelagoProgressiveDL_vtx_1 b/soh/assets/custom/objects/object_archipelago_item/gArchipelagoProgressiveDL_vtx_1 new file mode 100644 index 00000000000..9339e42f05c --- /dev/null +++ b/soh/assets/custom/objects/object_archipelago_item/gArchipelagoProgressiveDL_vtx_1 @@ -0,0 +1,77 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_archipelago_item/gArchipelagoProgressiveDL_vtx_2 b/soh/assets/custom/objects/object_archipelago_item/gArchipelagoProgressiveDL_vtx_2 new file mode 100644 index 00000000000..e1122533a0b --- /dev/null +++ b/soh/assets/custom/objects/object_archipelago_item/gArchipelagoProgressiveDL_vtx_2 @@ -0,0 +1,70 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_archipelago_item/gArchipelagoProgressiveDL_vtx_3 b/soh/assets/custom/objects/object_archipelago_item/gArchipelagoProgressiveDL_vtx_3 new file mode 100644 index 00000000000..29c025d0fdb --- /dev/null +++ b/soh/assets/custom/objects/object_archipelago_item/gArchipelagoProgressiveDL_vtx_3 @@ -0,0 +1,68 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_archipelago_item/gArchipelagoProgressiveDL_vtx_4 b/soh/assets/custom/objects/object_archipelago_item/gArchipelagoProgressiveDL_vtx_4 new file mode 100644 index 00000000000..c3c88918b35 --- /dev/null +++ b/soh/assets/custom/objects/object_archipelago_item/gArchipelagoProgressiveDL_vtx_4 @@ -0,0 +1,69 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_archipelago_item/gArchipelagoProgressiveDL_vtx_5 b/soh/assets/custom/objects/object_archipelago_item/gArchipelagoProgressiveDL_vtx_5 new file mode 100644 index 00000000000..70117ba12dc --- /dev/null +++ b/soh/assets/custom/objects/object_archipelago_item/gArchipelagoProgressiveDL_vtx_5 @@ -0,0 +1,64 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_archipelago_item/gArchipelagoProgressiveDL_vtx_6 b/soh/assets/custom/objects/object_archipelago_item/gArchipelagoProgressiveDL_vtx_6 new file mode 100644 index 00000000000..b76b5b31273 --- /dev/null +++ b/soh/assets/custom/objects/object_archipelago_item/gArchipelagoProgressiveDL_vtx_6 @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_archipelago_item/gArchipelagoProgressiveDL_vtx_7 b/soh/assets/custom/objects/object_archipelago_item/gArchipelagoProgressiveDL_vtx_7 new file mode 100644 index 00000000000..b76b5b31273 --- /dev/null +++ b/soh/assets/custom/objects/object_archipelago_item/gArchipelagoProgressiveDL_vtx_7 @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_archipelago_item/mat_gArchipelagoItemDL_black b/soh/assets/custom/objects/object_archipelago_item/mat_gArchipelagoItemDL_black new file mode 100644 index 00000000000..7ef279db504 --- /dev/null +++ b/soh/assets/custom/objects/object_archipelago_item/mat_gArchipelagoItemDL_black @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_archipelago_item/mat_gArchipelagoItemDL_blue b/soh/assets/custom/objects/object_archipelago_item/mat_gArchipelagoItemDL_blue new file mode 100644 index 00000000000..770d70e48bd --- /dev/null +++ b/soh/assets/custom/objects/object_archipelago_item/mat_gArchipelagoItemDL_blue @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_archipelago_item/mat_gArchipelagoItemDL_green b/soh/assets/custom/objects/object_archipelago_item/mat_gArchipelagoItemDL_green new file mode 100644 index 00000000000..9a57f3f8ccd --- /dev/null +++ b/soh/assets/custom/objects/object_archipelago_item/mat_gArchipelagoItemDL_green @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_archipelago_item/mat_gArchipelagoItemDL_orange b/soh/assets/custom/objects/object_archipelago_item/mat_gArchipelagoItemDL_orange new file mode 100644 index 00000000000..c3da3eff1f1 --- /dev/null +++ b/soh/assets/custom/objects/object_archipelago_item/mat_gArchipelagoItemDL_orange @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_archipelago_item/mat_gArchipelagoItemDL_outline b/soh/assets/custom/objects/object_archipelago_item/mat_gArchipelagoItemDL_outline new file mode 100644 index 00000000000..d39d1af92e8 --- /dev/null +++ b/soh/assets/custom/objects/object_archipelago_item/mat_gArchipelagoItemDL_outline @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_archipelago_item/mat_gArchipelagoItemDL_pink b/soh/assets/custom/objects/object_archipelago_item/mat_gArchipelagoItemDL_pink new file mode 100644 index 00000000000..e3b896b02c3 --- /dev/null +++ b/soh/assets/custom/objects/object_archipelago_item/mat_gArchipelagoItemDL_pink @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_archipelago_item/mat_gArchipelagoItemDL_purple b/soh/assets/custom/objects/object_archipelago_item/mat_gArchipelagoItemDL_purple new file mode 100644 index 00000000000..64f36202658 --- /dev/null +++ b/soh/assets/custom/objects/object_archipelago_item/mat_gArchipelagoItemDL_purple @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_archipelago_item/mat_gArchipelagoItemDL_red b/soh/assets/custom/objects/object_archipelago_item/mat_gArchipelagoItemDL_red new file mode 100644 index 00000000000..8cc9fec5e12 --- /dev/null +++ b/soh/assets/custom/objects/object_archipelago_item/mat_gArchipelagoItemDL_red @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_archipelago_item/mat_gArchipelagoItemDL_yellow b/soh/assets/custom/objects/object_archipelago_item/mat_gArchipelagoItemDL_yellow new file mode 100644 index 00000000000..1b0d4be9249 --- /dev/null +++ b/soh/assets/custom/objects/object_archipelago_item/mat_gArchipelagoItemDL_yellow @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_archipelago_item/mat_gArchipelagoJunkDL_black_junk b/soh/assets/custom/objects/object_archipelago_item/mat_gArchipelagoJunkDL_black_junk new file mode 100644 index 00000000000..7ef279db504 --- /dev/null +++ b/soh/assets/custom/objects/object_archipelago_item/mat_gArchipelagoJunkDL_black_junk @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_archipelago_item/mat_gArchipelagoJunkDL_blue_junk b/soh/assets/custom/objects/object_archipelago_item/mat_gArchipelagoJunkDL_blue_junk new file mode 100644 index 00000000000..b8f17f3bfac --- /dev/null +++ b/soh/assets/custom/objects/object_archipelago_item/mat_gArchipelagoJunkDL_blue_junk @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_archipelago_item/mat_gArchipelagoJunkDL_green_junk b/soh/assets/custom/objects/object_archipelago_item/mat_gArchipelagoJunkDL_green_junk new file mode 100644 index 00000000000..05312bc768a --- /dev/null +++ b/soh/assets/custom/objects/object_archipelago_item/mat_gArchipelagoJunkDL_green_junk @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_archipelago_item/mat_gArchipelagoJunkDL_grey_blue b/soh/assets/custom/objects/object_archipelago_item/mat_gArchipelagoJunkDL_grey_blue new file mode 100644 index 00000000000..a55a1459a5d --- /dev/null +++ b/soh/assets/custom/objects/object_archipelago_item/mat_gArchipelagoJunkDL_grey_blue @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_archipelago_item/mat_gArchipelagoJunkDL_grey_green b/soh/assets/custom/objects/object_archipelago_item/mat_gArchipelagoJunkDL_grey_green new file mode 100644 index 00000000000..20d667aa3f8 --- /dev/null +++ b/soh/assets/custom/objects/object_archipelago_item/mat_gArchipelagoJunkDL_grey_green @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_archipelago_item/mat_gArchipelagoJunkDL_grey_orange b/soh/assets/custom/objects/object_archipelago_item/mat_gArchipelagoJunkDL_grey_orange new file mode 100644 index 00000000000..ad2a5ac3896 --- /dev/null +++ b/soh/assets/custom/objects/object_archipelago_item/mat_gArchipelagoJunkDL_grey_orange @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_archipelago_item/mat_gArchipelagoJunkDL_grey_outline b/soh/assets/custom/objects/object_archipelago_item/mat_gArchipelagoJunkDL_grey_outline new file mode 100644 index 00000000000..d39d1af92e8 --- /dev/null +++ b/soh/assets/custom/objects/object_archipelago_item/mat_gArchipelagoJunkDL_grey_outline @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_archipelago_item/mat_gArchipelagoJunkDL_grey_pink b/soh/assets/custom/objects/object_archipelago_item/mat_gArchipelagoJunkDL_grey_pink new file mode 100644 index 00000000000..a3d5464833b --- /dev/null +++ b/soh/assets/custom/objects/object_archipelago_item/mat_gArchipelagoJunkDL_grey_pink @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_archipelago_item/mat_gArchipelagoJunkDL_grey_red b/soh/assets/custom/objects/object_archipelago_item/mat_gArchipelagoJunkDL_grey_red new file mode 100644 index 00000000000..78236fa287c --- /dev/null +++ b/soh/assets/custom/objects/object_archipelago_item/mat_gArchipelagoJunkDL_grey_red @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_archipelago_item/mat_gArchipelagoJunkDL_grey_yellow b/soh/assets/custom/objects/object_archipelago_item/mat_gArchipelagoJunkDL_grey_yellow new file mode 100644 index 00000000000..8def121b727 --- /dev/null +++ b/soh/assets/custom/objects/object_archipelago_item/mat_gArchipelagoJunkDL_grey_yellow @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_archipelago_item/mat_gArchipelagoJunkDL_orange_junk b/soh/assets/custom/objects/object_archipelago_item/mat_gArchipelagoJunkDL_orange_junk new file mode 100644 index 00000000000..17867ed9c9e --- /dev/null +++ b/soh/assets/custom/objects/object_archipelago_item/mat_gArchipelagoJunkDL_orange_junk @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_archipelago_item/mat_gArchipelagoJunkDL_purple_junk b/soh/assets/custom/objects/object_archipelago_item/mat_gArchipelagoJunkDL_purple_junk new file mode 100644 index 00000000000..7a880f3e9b6 --- /dev/null +++ b/soh/assets/custom/objects/object_archipelago_item/mat_gArchipelagoJunkDL_purple_junk @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_archipelago_item/mat_gArchipelagoJunkDL_red_junk b/soh/assets/custom/objects/object_archipelago_item/mat_gArchipelagoJunkDL_red_junk new file mode 100644 index 00000000000..20b76364352 --- /dev/null +++ b/soh/assets/custom/objects/object_archipelago_item/mat_gArchipelagoJunkDL_red_junk @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_archipelago_item/mat_gArchipelagoJunkDL_yellow_junk b/soh/assets/custom/objects/object_archipelago_item/mat_gArchipelagoJunkDL_yellow_junk new file mode 100644 index 00000000000..7e88e53d727 --- /dev/null +++ b/soh/assets/custom/objects/object_archipelago_item/mat_gArchipelagoJunkDL_yellow_junk @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_archipelago_item/mat_gArchipelagoProgressiveDL_arrow_gold_progressive b/soh/assets/custom/objects/object_archipelago_item/mat_gArchipelagoProgressiveDL_arrow_gold_progressive new file mode 100644 index 00000000000..fa76abb083e --- /dev/null +++ b/soh/assets/custom/objects/object_archipelago_item/mat_gArchipelagoProgressiveDL_arrow_gold_progressive @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_archipelago_item/mat_gArchipelagoProgressiveDL_black_progressive b/soh/assets/custom/objects/object_archipelago_item/mat_gArchipelagoProgressiveDL_black_progressive new file mode 100644 index 00000000000..7ef279db504 --- /dev/null +++ b/soh/assets/custom/objects/object_archipelago_item/mat_gArchipelagoProgressiveDL_black_progressive @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_archipelago_item/mat_gArchipelagoProgressiveDL_blue_progressive b/soh/assets/custom/objects/object_archipelago_item/mat_gArchipelagoProgressiveDL_blue_progressive new file mode 100644 index 00000000000..770d70e48bd --- /dev/null +++ b/soh/assets/custom/objects/object_archipelago_item/mat_gArchipelagoProgressiveDL_blue_progressive @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_archipelago_item/mat_gArchipelagoProgressiveDL_green_progressive b/soh/assets/custom/objects/object_archipelago_item/mat_gArchipelagoProgressiveDL_green_progressive new file mode 100644 index 00000000000..9a57f3f8ccd --- /dev/null +++ b/soh/assets/custom/objects/object_archipelago_item/mat_gArchipelagoProgressiveDL_green_progressive @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_archipelago_item/mat_gArchipelagoProgressiveDL_orange_progressive b/soh/assets/custom/objects/object_archipelago_item/mat_gArchipelagoProgressiveDL_orange_progressive new file mode 100644 index 00000000000..c3da3eff1f1 --- /dev/null +++ b/soh/assets/custom/objects/object_archipelago_item/mat_gArchipelagoProgressiveDL_orange_progressive @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_archipelago_item/mat_gArchipelagoProgressiveDL_progressive_arrow b/soh/assets/custom/objects/object_archipelago_item/mat_gArchipelagoProgressiveDL_progressive_arrow new file mode 100644 index 00000000000..c41b05db9a4 --- /dev/null +++ b/soh/assets/custom/objects/object_archipelago_item/mat_gArchipelagoProgressiveDL_progressive_arrow @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_archipelago_item/mat_gArchipelagoProgressiveDL_progressive_blue b/soh/assets/custom/objects/object_archipelago_item/mat_gArchipelagoProgressiveDL_progressive_blue new file mode 100644 index 00000000000..c9073d0cfc4 --- /dev/null +++ b/soh/assets/custom/objects/object_archipelago_item/mat_gArchipelagoProgressiveDL_progressive_blue @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_archipelago_item/mat_gArchipelagoProgressiveDL_progressive_green b/soh/assets/custom/objects/object_archipelago_item/mat_gArchipelagoProgressiveDL_progressive_green new file mode 100644 index 00000000000..84e26fe7a72 --- /dev/null +++ b/soh/assets/custom/objects/object_archipelago_item/mat_gArchipelagoProgressiveDL_progressive_green @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_archipelago_item/mat_gArchipelagoProgressiveDL_progressive_orange b/soh/assets/custom/objects/object_archipelago_item/mat_gArchipelagoProgressiveDL_progressive_orange new file mode 100644 index 00000000000..076acfe28ce --- /dev/null +++ b/soh/assets/custom/objects/object_archipelago_item/mat_gArchipelagoProgressiveDL_progressive_orange @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_archipelago_item/mat_gArchipelagoProgressiveDL_progressive_outline b/soh/assets/custom/objects/object_archipelago_item/mat_gArchipelagoProgressiveDL_progressive_outline new file mode 100644 index 00000000000..d39d1af92e8 --- /dev/null +++ b/soh/assets/custom/objects/object_archipelago_item/mat_gArchipelagoProgressiveDL_progressive_outline @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_archipelago_item/mat_gArchipelagoProgressiveDL_progressive_pink b/soh/assets/custom/objects/object_archipelago_item/mat_gArchipelagoProgressiveDL_progressive_pink new file mode 100644 index 00000000000..e3b896b02c3 --- /dev/null +++ b/soh/assets/custom/objects/object_archipelago_item/mat_gArchipelagoProgressiveDL_progressive_pink @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_archipelago_item/mat_gArchipelagoProgressiveDL_progressive_red b/soh/assets/custom/objects/object_archipelago_item/mat_gArchipelagoProgressiveDL_progressive_red new file mode 100644 index 00000000000..930fb41e6c5 --- /dev/null +++ b/soh/assets/custom/objects/object_archipelago_item/mat_gArchipelagoProgressiveDL_progressive_red @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_archipelago_item/mat_gArchipelagoProgressiveDL_progressive_yellow b/soh/assets/custom/objects/object_archipelago_item/mat_gArchipelagoProgressiveDL_progressive_yellow new file mode 100644 index 00000000000..383d9441caa --- /dev/null +++ b/soh/assets/custom/objects/object_archipelago_item/mat_gArchipelagoProgressiveDL_progressive_yellow @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_archipelago_item/mat_gArchipelagoProgressiveDL_purple_progressive b/soh/assets/custom/objects/object_archipelago_item/mat_gArchipelagoProgressiveDL_purple_progressive new file mode 100644 index 00000000000..64f36202658 --- /dev/null +++ b/soh/assets/custom/objects/object_archipelago_item/mat_gArchipelagoProgressiveDL_purple_progressive @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_archipelago_item/mat_gArchipelagoProgressiveDL_red_progressive b/soh/assets/custom/objects/object_archipelago_item/mat_gArchipelagoProgressiveDL_red_progressive new file mode 100644 index 00000000000..8cc9fec5e12 --- /dev/null +++ b/soh/assets/custom/objects/object_archipelago_item/mat_gArchipelagoProgressiveDL_red_progressive @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_archipelago_item/mat_gArchipelagoProgressiveDL_yellow_progressive b/soh/assets/custom/objects/object_archipelago_item/mat_gArchipelagoProgressiveDL_yellow_progressive new file mode 100644 index 00000000000..1b0d4be9249 --- /dev/null +++ b/soh/assets/custom/objects/object_archipelago_item/mat_gArchipelagoProgressiveDL_yellow_progressive @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_archipelago_item/model.xml b/soh/assets/custom/objects/object_archipelago_item/model.xml new file mode 100644 index 00000000000..e69de29bb2d diff --git a/soh/assets/custom/objects/object_festivehats/antlers_64 b/soh/assets/custom/objects/object_festivehats/antlers_64 new file mode 100644 index 00000000000..74fe9e81977 Binary files /dev/null and b/soh/assets/custom/objects/object_festivehats/antlers_64 differ diff --git a/soh/assets/custom/objects/object_festivehats/gCuccoLadyHatDL b/soh/assets/custom/objects/object_festivehats/gCuccoLadyHatDL new file mode 100644 index 00000000000..ab8fdf45f29 --- /dev/null +++ b/soh/assets/custom/objects/object_festivehats/gCuccoLadyHatDL @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_festivehats/gCuccoLadyHatDL_tri_0 b/soh/assets/custom/objects/object_festivehats/gCuccoLadyHatDL_tri_0 new file mode 100644 index 00000000000..980452845c3 --- /dev/null +++ b/soh/assets/custom/objects/object_festivehats/gCuccoLadyHatDL_tri_0 @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_festivehats/gCuccoLadyHatDL_vtx_0 b/soh/assets/custom/objects/object_festivehats/gCuccoLadyHatDL_vtx_0 new file mode 100644 index 00000000000..8a64cdf18f5 --- /dev/null +++ b/soh/assets/custom/objects/object_festivehats/gCuccoLadyHatDL_vtx_0 @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_festivehats/gEponaRudolphHatDL b/soh/assets/custom/objects/object_festivehats/gEponaRudolphHatDL new file mode 100644 index 00000000000..6d059179fdb --- /dev/null +++ b/soh/assets/custom/objects/object_festivehats/gEponaRudolphHatDL @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_festivehats/gEponaRudolphHatDL_tri_0 b/soh/assets/custom/objects/object_festivehats/gEponaRudolphHatDL_tri_0 new file mode 100644 index 00000000000..3e0ab2ad8f3 --- /dev/null +++ b/soh/assets/custom/objects/object_festivehats/gEponaRudolphHatDL_tri_0 @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_festivehats/gEponaRudolphHatDL_tri_1 b/soh/assets/custom/objects/object_festivehats/gEponaRudolphHatDL_tri_1 new file mode 100644 index 00000000000..99f0a799deb --- /dev/null +++ b/soh/assets/custom/objects/object_festivehats/gEponaRudolphHatDL_tri_1 @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_festivehats/gEponaRudolphHatDL_vtx_0 b/soh/assets/custom/objects/object_festivehats/gEponaRudolphHatDL_vtx_0 new file mode 100644 index 00000000000..ebb2b0eeac3 --- /dev/null +++ b/soh/assets/custom/objects/object_festivehats/gEponaRudolphHatDL_vtx_0 @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_festivehats/gEponaRudolphHatDL_vtx_1 b/soh/assets/custom/objects/object_festivehats/gEponaRudolphHatDL_vtx_1 new file mode 100644 index 00000000000..d7263bc946b --- /dev/null +++ b/soh/assets/custom/objects/object_festivehats/gEponaRudolphHatDL_vtx_1 @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_festivehats/gHorseAntlersDL b/soh/assets/custom/objects/object_festivehats/gHorseAntlersDL new file mode 100644 index 00000000000..2b2ee6c3e6a --- /dev/null +++ b/soh/assets/custom/objects/object_festivehats/gHorseAntlersDL @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_festivehats/gHorseAntlersDL_tri_0 b/soh/assets/custom/objects/object_festivehats/gHorseAntlersDL_tri_0 new file mode 100644 index 00000000000..543e90c1f4b --- /dev/null +++ b/soh/assets/custom/objects/object_festivehats/gHorseAntlersDL_tri_0 @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_festivehats/gHorseAntlersDL_vtx_0 b/soh/assets/custom/objects/object_festivehats/gHorseAntlersDL_vtx_0 new file mode 100644 index 00000000000..d7263bc946b --- /dev/null +++ b/soh/assets/custom/objects/object_festivehats/gHorseAntlersDL_vtx_0 @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_festivehats/gLinkAdultHatTrimDL b/soh/assets/custom/objects/object_festivehats/gLinkAdultHatTrimDL new file mode 100644 index 00000000000..27bdd9211b2 --- /dev/null +++ b/soh/assets/custom/objects/object_festivehats/gLinkAdultHatTrimDL @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_festivehats/gLinkAdultHatTrimDL_tri_0 b/soh/assets/custom/objects/object_festivehats/gLinkAdultHatTrimDL_tri_0 new file mode 100644 index 00000000000..313f489de85 --- /dev/null +++ b/soh/assets/custom/objects/object_festivehats/gLinkAdultHatTrimDL_tri_0 @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_festivehats/gLinkAdultHatTrimDL_vtx_0 b/soh/assets/custom/objects/object_festivehats/gLinkAdultHatTrimDL_vtx_0 new file mode 100644 index 00000000000..eb7d042ff69 --- /dev/null +++ b/soh/assets/custom/objects/object_festivehats/gLinkAdultHatTrimDL_vtx_0 @@ -0,0 +1,54 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_festivehats/gLinkAdultPompomDL b/soh/assets/custom/objects/object_festivehats/gLinkAdultPompomDL new file mode 100644 index 00000000000..6f72624513b --- /dev/null +++ b/soh/assets/custom/objects/object_festivehats/gLinkAdultPompomDL @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_festivehats/gLinkAdultPompomDL_tri_0 b/soh/assets/custom/objects/object_festivehats/gLinkAdultPompomDL_tri_0 new file mode 100644 index 00000000000..83cf6d929d6 --- /dev/null +++ b/soh/assets/custom/objects/object_festivehats/gLinkAdultPompomDL_tri_0 @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_festivehats/gLinkAdultPompomDL_vtx_0 b/soh/assets/custom/objects/object_festivehats/gLinkAdultPompomDL_vtx_0 new file mode 100644 index 00000000000..b937dad3e38 --- /dev/null +++ b/soh/assets/custom/objects/object_festivehats/gLinkAdultPompomDL_vtx_0 @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_festivehats/gLinkChildHatTrimDL b/soh/assets/custom/objects/object_festivehats/gLinkChildHatTrimDL new file mode 100644 index 00000000000..769a4192892 --- /dev/null +++ b/soh/assets/custom/objects/object_festivehats/gLinkChildHatTrimDL @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_festivehats/gLinkChildHatTrimDL_tri_0 b/soh/assets/custom/objects/object_festivehats/gLinkChildHatTrimDL_tri_0 new file mode 100644 index 00000000000..776fb4da9e8 --- /dev/null +++ b/soh/assets/custom/objects/object_festivehats/gLinkChildHatTrimDL_tri_0 @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_festivehats/gLinkChildHatTrimDL_vtx_0 b/soh/assets/custom/objects/object_festivehats/gLinkChildHatTrimDL_vtx_0 new file mode 100644 index 00000000000..847f1ae8164 --- /dev/null +++ b/soh/assets/custom/objects/object_festivehats/gLinkChildHatTrimDL_vtx_0 @@ -0,0 +1,59 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_festivehats/gLinkChildPompomDL b/soh/assets/custom/objects/object_festivehats/gLinkChildPompomDL new file mode 100644 index 00000000000..4093e1aa37a --- /dev/null +++ b/soh/assets/custom/objects/object_festivehats/gLinkChildPompomDL @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_festivehats/gLinkChildPompomDL_tri_0 b/soh/assets/custom/objects/object_festivehats/gLinkChildPompomDL_tri_0 new file mode 100644 index 00000000000..4f18af8bf8a --- /dev/null +++ b/soh/assets/custom/objects/object_festivehats/gLinkChildPompomDL_tri_0 @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_festivehats/gLinkChildPompomDL_vtx_0 b/soh/assets/custom/objects/object_festivehats/gLinkChildPompomDL_vtx_0 new file mode 100644 index 00000000000..b0c371f1350 --- /dev/null +++ b/soh/assets/custom/objects/object_festivehats/gLinkChildPompomDL_vtx_0 @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_festivehats/gPaperCrownGenericDL b/soh/assets/custom/objects/object_festivehats/gPaperCrownGenericDL new file mode 100644 index 00000000000..ec6ccada7f5 --- /dev/null +++ b/soh/assets/custom/objects/object_festivehats/gPaperCrownGenericDL @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_festivehats/gPaperCrownGenericDL_tri_0 b/soh/assets/custom/objects/object_festivehats/gPaperCrownGenericDL_tri_0 new file mode 100644 index 00000000000..672602bc15c --- /dev/null +++ b/soh/assets/custom/objects/object_festivehats/gPaperCrownGenericDL_tri_0 @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_festivehats/gPaperCrownGenericDL_vtx_0 b/soh/assets/custom/objects/object_festivehats/gPaperCrownGenericDL_vtx_0 new file mode 100644 index 00000000000..c7a513a67fb --- /dev/null +++ b/soh/assets/custom/objects/object_festivehats/gPaperCrownGenericDL_vtx_0 @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_festivehats/gSantaHatGenericDL b/soh/assets/custom/objects/object_festivehats/gSantaHatGenericDL new file mode 100644 index 00000000000..0f457397652 --- /dev/null +++ b/soh/assets/custom/objects/object_festivehats/gSantaHatGenericDL @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_festivehats/gSantaHatGenericDL_tri_0 b/soh/assets/custom/objects/object_festivehats/gSantaHatGenericDL_tri_0 new file mode 100644 index 00000000000..5396ff4d8a9 --- /dev/null +++ b/soh/assets/custom/objects/object_festivehats/gSantaHatGenericDL_tri_0 @@ -0,0 +1,62 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_festivehats/gSantaHatGenericDL_tri_1 b/soh/assets/custom/objects/object_festivehats/gSantaHatGenericDL_tri_1 new file mode 100644 index 00000000000..bdf895c49f6 --- /dev/null +++ b/soh/assets/custom/objects/object_festivehats/gSantaHatGenericDL_tri_1 @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_festivehats/gSantaHatGenericDL_vtx_0 b/soh/assets/custom/objects/object_festivehats/gSantaHatGenericDL_vtx_0 new file mode 100644 index 00000000000..145ca70f4fe --- /dev/null +++ b/soh/assets/custom/objects/object_festivehats/gSantaHatGenericDL_vtx_0 @@ -0,0 +1,63 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_festivehats/gSantaHatGenericDL_vtx_1 b/soh/assets/custom/objects/object_festivehats/gSantaHatGenericDL_vtx_1 new file mode 100644 index 00000000000..7f90daac735 --- /dev/null +++ b/soh/assets/custom/objects/object_festivehats/gSantaHatGenericDL_vtx_1 @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_festivehats/hilite_nose b/soh/assets/custom/objects/object_festivehats/hilite_nose new file mode 100644 index 00000000000..b2d4623c9c7 Binary files /dev/null and b/soh/assets/custom/objects/object_festivehats/hilite_nose differ diff --git a/soh/assets/custom/objects/object_festivehats/mat_gCuccoLadyHatDL_f3dlite_hatcolour b/soh/assets/custom/objects/object_festivehats/mat_gCuccoLadyHatDL_f3dlite_hatcolour new file mode 100644 index 00000000000..d1d036f8961 --- /dev/null +++ b/soh/assets/custom/objects/object_festivehats/mat_gCuccoLadyHatDL_f3dlite_hatcolour @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_festivehats/mat_gEponaRudolphHatDL_f3dlite_antlers b/soh/assets/custom/objects/object_festivehats/mat_gEponaRudolphHatDL_f3dlite_antlers new file mode 100644 index 00000000000..3717c72b2e3 --- /dev/null +++ b/soh/assets/custom/objects/object_festivehats/mat_gEponaRudolphHatDL_f3dlite_antlers @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_festivehats/mat_gEponaRudolphHatDL_f3dlite_rednose b/soh/assets/custom/objects/object_festivehats/mat_gEponaRudolphHatDL_f3dlite_rednose new file mode 100644 index 00000000000..758d8698be8 --- /dev/null +++ b/soh/assets/custom/objects/object_festivehats/mat_gEponaRudolphHatDL_f3dlite_rednose @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_festivehats/mat_gHorseAntlersDL_f3dlite_antlers b/soh/assets/custom/objects/object_festivehats/mat_gHorseAntlersDL_f3dlite_antlers new file mode 100644 index 00000000000..3717c72b2e3 --- /dev/null +++ b/soh/assets/custom/objects/object_festivehats/mat_gHorseAntlersDL_f3dlite_antlers @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_festivehats/mat_gLinkAdultHatTrimDL_f3dlite_santahat_white b/soh/assets/custom/objects/object_festivehats/mat_gLinkAdultHatTrimDL_f3dlite_santahat_white new file mode 100644 index 00000000000..2f72783f760 --- /dev/null +++ b/soh/assets/custom/objects/object_festivehats/mat_gLinkAdultHatTrimDL_f3dlite_santahat_white @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_festivehats/mat_gLinkAdultPompomDL_f3dlite_santahat_white b/soh/assets/custom/objects/object_festivehats/mat_gLinkAdultPompomDL_f3dlite_santahat_white new file mode 100644 index 00000000000..2f72783f760 --- /dev/null +++ b/soh/assets/custom/objects/object_festivehats/mat_gLinkAdultPompomDL_f3dlite_santahat_white @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_festivehats/mat_gLinkChildHatTrimDL_f3dlite_santahat_white b/soh/assets/custom/objects/object_festivehats/mat_gLinkChildHatTrimDL_f3dlite_santahat_white new file mode 100644 index 00000000000..2f72783f760 --- /dev/null +++ b/soh/assets/custom/objects/object_festivehats/mat_gLinkChildHatTrimDL_f3dlite_santahat_white @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_festivehats/mat_gLinkChildPompomDL_f3dlite_santahat_white b/soh/assets/custom/objects/object_festivehats/mat_gLinkChildPompomDL_f3dlite_santahat_white new file mode 100644 index 00000000000..2f72783f760 --- /dev/null +++ b/soh/assets/custom/objects/object_festivehats/mat_gLinkChildPompomDL_f3dlite_santahat_white @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_festivehats/mat_gPaperCrownGenericDL_f3dlite_crown b/soh/assets/custom/objects/object_festivehats/mat_gPaperCrownGenericDL_f3dlite_crown new file mode 100644 index 00000000000..4fb4e2029cb --- /dev/null +++ b/soh/assets/custom/objects/object_festivehats/mat_gPaperCrownGenericDL_f3dlite_crown @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_festivehats/mat_gSantaHatGenericDL_f3dlite_santahatred b/soh/assets/custom/objects/object_festivehats/mat_gSantaHatGenericDL_f3dlite_santahatred new file mode 100644 index 00000000000..b42644ae815 --- /dev/null +++ b/soh/assets/custom/objects/object_festivehats/mat_gSantaHatGenericDL_f3dlite_santahatred @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_festivehats/mat_gSantaHatGenericDL_f3dlite_santahatwhite b/soh/assets/custom/objects/object_festivehats/mat_gSantaHatGenericDL_f3dlite_santahatwhite new file mode 100644 index 00000000000..2f72783f760 --- /dev/null +++ b/soh/assets/custom/objects/object_festivehats/mat_gSantaHatGenericDL_f3dlite_santahatwhite @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_holiday_completed/GlowAlpha_64 b/soh/assets/custom/objects/object_holiday_completed/GlowAlpha_64 new file mode 100644 index 00000000000..1682d9e6f2f Binary files /dev/null and b/soh/assets/custom/objects/object_holiday_completed/GlowAlpha_64 differ diff --git a/soh/assets/custom/objects/object_holiday_completed/gHolidayPieceCompletedDL b/soh/assets/custom/objects/object_holiday_completed/gHolidayPieceCompletedDL new file mode 100644 index 00000000000..2f4f95446c5 --- /dev/null +++ b/soh/assets/custom/objects/object_holiday_completed/gHolidayPieceCompletedDL @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_holiday_completed/gHolidayPieceCompletedDL_tri_0 b/soh/assets/custom/objects/object_holiday_completed/gHolidayPieceCompletedDL_tri_0 new file mode 100644 index 00000000000..41e2e426873 --- /dev/null +++ b/soh/assets/custom/objects/object_holiday_completed/gHolidayPieceCompletedDL_tri_0 @@ -0,0 +1,7 @@ + + + + + + + diff --git a/soh/assets/custom/objects/object_holiday_completed/gHolidayPieceCompletedDL_tri_1 b/soh/assets/custom/objects/object_holiday_completed/gHolidayPieceCompletedDL_tri_1 new file mode 100644 index 00000000000..e181646da0a --- /dev/null +++ b/soh/assets/custom/objects/object_holiday_completed/gHolidayPieceCompletedDL_tri_1 @@ -0,0 +1,390 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_holiday_completed/gHolidayPieceCompletedDL_vtx_0 b/soh/assets/custom/objects/object_holiday_completed/gHolidayPieceCompletedDL_vtx_0 new file mode 100644 index 00000000000..ca11ab1b3b6 --- /dev/null +++ b/soh/assets/custom/objects/object_holiday_completed/gHolidayPieceCompletedDL_vtx_0 @@ -0,0 +1,6 @@ + + + + + + diff --git a/soh/assets/custom/objects/object_holiday_completed/gHolidayPieceCompletedDL_vtx_1 b/soh/assets/custom/objects/object_holiday_completed/gHolidayPieceCompletedDL_vtx_1 new file mode 100644 index 00000000000..cc747f74a3c --- /dev/null +++ b/soh/assets/custom/objects/object_holiday_completed/gHolidayPieceCompletedDL_vtx_1 @@ -0,0 +1,347 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_holiday_completed/hilite_melon b/soh/assets/custom/objects/object_holiday_completed/hilite_melon new file mode 100644 index 00000000000..df1d605b38e Binary files /dev/null and b/soh/assets/custom/objects/object_holiday_completed/hilite_melon differ diff --git a/soh/assets/custom/objects/object_holiday_completed/mat_gHolidayPieceCompletedDL_f3dlite_Glow b/soh/assets/custom/objects/object_holiday_completed/mat_gHolidayPieceCompletedDL_f3dlite_Glow new file mode 100644 index 00000000000..bbd0a95f17a --- /dev/null +++ b/soh/assets/custom/objects/object_holiday_completed/mat_gHolidayPieceCompletedDL_f3dlite_Glow @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_holiday_completed/mat_gHolidayPieceCompletedDL_f3dlite_OrnamentGold b/soh/assets/custom/objects/object_holiday_completed/mat_gHolidayPieceCompletedDL_f3dlite_OrnamentGold new file mode 100644 index 00000000000..48501c1d646 --- /dev/null +++ b/soh/assets/custom/objects/object_holiday_completed/mat_gHolidayPieceCompletedDL_f3dlite_OrnamentGold @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_holiday_completed/mat_gHolidayPieceCompletedDL_f3dlite_holiday_edges b/soh/assets/custom/objects/object_holiday_completed/mat_gHolidayPieceCompletedDL_f3dlite_holiday_edges new file mode 100644 index 00000000000..ffb7d0e2ef2 --- /dev/null +++ b/soh/assets/custom/objects/object_holiday_completed/mat_gHolidayPieceCompletedDL_f3dlite_holiday_edges @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_holiday_completed/mat_gHolidayPieceCompletedDL_f3dlite_holiday_surface b/soh/assets/custom/objects/object_holiday_completed/mat_gHolidayPieceCompletedDL_f3dlite_holiday_surface new file mode 100644 index 00000000000..3c5c8226551 --- /dev/null +++ b/soh/assets/custom/objects/object_holiday_completed/mat_gHolidayPieceCompletedDL_f3dlite_holiday_surface @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_holiday_completed/noise_tex b/soh/assets/custom/objects/object_holiday_completed/noise_tex new file mode 100644 index 00000000000..a6d6cf945e1 Binary files /dev/null and b/soh/assets/custom/objects/object_holiday_completed/noise_tex differ diff --git a/soh/assets/custom/objects/object_holiday_piece_0/gHolidayPiece0DL b/soh/assets/custom/objects/object_holiday_piece_0/gHolidayPiece0DL new file mode 100644 index 00000000000..19dc3d2b772 --- /dev/null +++ b/soh/assets/custom/objects/object_holiday_piece_0/gHolidayPiece0DL @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_holiday_piece_0/gHolidayPiece0DL_tri_0 b/soh/assets/custom/objects/object_holiday_piece_0/gHolidayPiece0DL_tri_0 new file mode 100644 index 00000000000..12c71b55b5f --- /dev/null +++ b/soh/assets/custom/objects/object_holiday_piece_0/gHolidayPiece0DL_tri_0 @@ -0,0 +1,67 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_holiday_piece_0/gHolidayPiece0DL_tri_1 b/soh/assets/custom/objects/object_holiday_piece_0/gHolidayPiece0DL_tri_1 new file mode 100644 index 00000000000..0dac1412f56 --- /dev/null +++ b/soh/assets/custom/objects/object_holiday_piece_0/gHolidayPiece0DL_tri_1 @@ -0,0 +1,91 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_holiday_piece_0/gHolidayPiece0DL_tri_2 b/soh/assets/custom/objects/object_holiday_piece_0/gHolidayPiece0DL_tri_2 new file mode 100644 index 00000000000..453204c07da --- /dev/null +++ b/soh/assets/custom/objects/object_holiday_piece_0/gHolidayPiece0DL_tri_2 @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_holiday_piece_0/gHolidayPiece0DL_vtx_0 b/soh/assets/custom/objects/object_holiday_piece_0/gHolidayPiece0DL_vtx_0 new file mode 100644 index 00000000000..4f60b10fe4b --- /dev/null +++ b/soh/assets/custom/objects/object_holiday_piece_0/gHolidayPiece0DL_vtx_0 @@ -0,0 +1,72 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_holiday_piece_0/gHolidayPiece0DL_vtx_1 b/soh/assets/custom/objects/object_holiday_piece_0/gHolidayPiece0DL_vtx_1 new file mode 100644 index 00000000000..80931181f14 --- /dev/null +++ b/soh/assets/custom/objects/object_holiday_piece_0/gHolidayPiece0DL_vtx_1 @@ -0,0 +1,107 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_holiday_piece_0/gHolidayPiece0DL_vtx_2 b/soh/assets/custom/objects/object_holiday_piece_0/gHolidayPiece0DL_vtx_2 new file mode 100644 index 00000000000..86d1238254d --- /dev/null +++ b/soh/assets/custom/objects/object_holiday_piece_0/gHolidayPiece0DL_vtx_2 @@ -0,0 +1,49 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_holiday_piece_0/image b/soh/assets/custom/objects/object_holiday_piece_0/image new file mode 100644 index 00000000000..2a8a62348be Binary files /dev/null and b/soh/assets/custom/objects/object_holiday_piece_0/image differ diff --git a/soh/assets/custom/objects/object_holiday_piece_0/mat_gHolidayPiece0DL_ball b/soh/assets/custom/objects/object_holiday_piece_0/mat_gHolidayPiece0DL_ball new file mode 100644 index 00000000000..2d1e89f4426 --- /dev/null +++ b/soh/assets/custom/objects/object_holiday_piece_0/mat_gHolidayPiece0DL_ball @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_holiday_piece_0/mat_gHolidayPiece0DL_f3dlite_holiday_edges b/soh/assets/custom/objects/object_holiday_piece_0/mat_gHolidayPiece0DL_f3dlite_holiday_edges new file mode 100644 index 00000000000..da13b468d19 --- /dev/null +++ b/soh/assets/custom/objects/object_holiday_piece_0/mat_gHolidayPiece0DL_f3dlite_holiday_edges @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_holiday_piece_0/mat_gHolidayPiece0DL_f3dlite_holiday_surface b/soh/assets/custom/objects/object_holiday_piece_0/mat_gHolidayPiece0DL_f3dlite_holiday_surface new file mode 100644 index 00000000000..7ae7dc06ae5 --- /dev/null +++ b/soh/assets/custom/objects/object_holiday_piece_0/mat_gHolidayPiece0DL_f3dlite_holiday_surface @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_holiday_piece_0/mat_gHolidayPiece0DL_f3dlite_shard_edge b/soh/assets/custom/objects/object_holiday_piece_0/mat_gHolidayPiece0DL_f3dlite_shard_edge new file mode 100644 index 00000000000..397332134a4 --- /dev/null +++ b/soh/assets/custom/objects/object_holiday_piece_0/mat_gHolidayPiece0DL_f3dlite_shard_edge @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_holiday_piece_0/mat_gHolidayPiece0DL_silver b/soh/assets/custom/objects/object_holiday_piece_0/mat_gHolidayPiece0DL_silver new file mode 100644 index 00000000000..fd95bf299df --- /dev/null +++ b/soh/assets/custom/objects/object_holiday_piece_0/mat_gHolidayPiece0DL_silver @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_holiday_piece_0/noise_tex b/soh/assets/custom/objects/object_holiday_piece_0/noise_tex new file mode 100644 index 00000000000..a6d6cf945e1 Binary files /dev/null and b/soh/assets/custom/objects/object_holiday_piece_0/noise_tex differ diff --git a/soh/assets/custom/objects/object_holiday_piece_1/gHolidayPiece1DL b/soh/assets/custom/objects/object_holiday_piece_1/gHolidayPiece1DL new file mode 100644 index 00000000000..10b1f88cfef --- /dev/null +++ b/soh/assets/custom/objects/object_holiday_piece_1/gHolidayPiece1DL @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_holiday_piece_1/gHolidayPiece1DL_tri_0 b/soh/assets/custom/objects/object_holiday_piece_1/gHolidayPiece1DL_tri_0 new file mode 100644 index 00000000000..b3ad7497bc0 --- /dev/null +++ b/soh/assets/custom/objects/object_holiday_piece_1/gHolidayPiece1DL_tri_0 @@ -0,0 +1,67 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_holiday_piece_1/gHolidayPiece1DL_tri_1 b/soh/assets/custom/objects/object_holiday_piece_1/gHolidayPiece1DL_tri_1 new file mode 100644 index 00000000000..61ff55dca78 --- /dev/null +++ b/soh/assets/custom/objects/object_holiday_piece_1/gHolidayPiece1DL_tri_1 @@ -0,0 +1,88 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_holiday_piece_1/gHolidayPiece1DL_vtx_0 b/soh/assets/custom/objects/object_holiday_piece_1/gHolidayPiece1DL_vtx_0 new file mode 100644 index 00000000000..366463fd9cc --- /dev/null +++ b/soh/assets/custom/objects/object_holiday_piece_1/gHolidayPiece1DL_vtx_0 @@ -0,0 +1,72 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_holiday_piece_1/gHolidayPiece1DL_vtx_1 b/soh/assets/custom/objects/object_holiday_piece_1/gHolidayPiece1DL_vtx_1 new file mode 100644 index 00000000000..8085ae95ab8 --- /dev/null +++ b/soh/assets/custom/objects/object_holiday_piece_1/gHolidayPiece1DL_vtx_1 @@ -0,0 +1,96 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_holiday_piece_1/image b/soh/assets/custom/objects/object_holiday_piece_1/image new file mode 100644 index 00000000000..2a8a62348be Binary files /dev/null and b/soh/assets/custom/objects/object_holiday_piece_1/image differ diff --git a/soh/assets/custom/objects/object_holiday_piece_1/image_copy b/soh/assets/custom/objects/object_holiday_piece_1/image_copy new file mode 100644 index 00000000000..2a8a62348be Binary files /dev/null and b/soh/assets/custom/objects/object_holiday_piece_1/image_copy differ diff --git a/soh/assets/custom/objects/object_holiday_piece_1/mat_gHolidayPiece1DL_blue_mat b/soh/assets/custom/objects/object_holiday_piece_1/mat_gHolidayPiece1DL_blue_mat new file mode 100644 index 00000000000..6891958fc1a --- /dev/null +++ b/soh/assets/custom/objects/object_holiday_piece_1/mat_gHolidayPiece1DL_blue_mat @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_holiday_piece_1/mat_gHolidayPiece1DL_f3dlite_holiday_surface b/soh/assets/custom/objects/object_holiday_piece_1/mat_gHolidayPiece1DL_f3dlite_holiday_surface new file mode 100644 index 00000000000..059d6551127 --- /dev/null +++ b/soh/assets/custom/objects/object_holiday_piece_1/mat_gHolidayPiece1DL_f3dlite_holiday_surface @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_holiday_piece_1/mat_gHolidayPiece1DL_f3dlite_shard_edge b/soh/assets/custom/objects/object_holiday_piece_1/mat_gHolidayPiece1DL_f3dlite_shard_edge new file mode 100644 index 00000000000..056bf5f6f24 --- /dev/null +++ b/soh/assets/custom/objects/object_holiday_piece_1/mat_gHolidayPiece1DL_f3dlite_shard_edge @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_holiday_piece_1/mat_gHolidayPiece1DL_silver_002 b/soh/assets/custom/objects/object_holiday_piece_1/mat_gHolidayPiece1DL_silver_002 new file mode 100644 index 00000000000..076e52b20b6 --- /dev/null +++ b/soh/assets/custom/objects/object_holiday_piece_1/mat_gHolidayPiece1DL_silver_002 @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_holiday_piece_1/noise_tex b/soh/assets/custom/objects/object_holiday_piece_1/noise_tex new file mode 100644 index 00000000000..a6d6cf945e1 Binary files /dev/null and b/soh/assets/custom/objects/object_holiday_piece_1/noise_tex differ diff --git a/soh/assets/custom/objects/object_holiday_piece_2/gHolidayPiece2DL b/soh/assets/custom/objects/object_holiday_piece_2/gHolidayPiece2DL new file mode 100644 index 00000000000..9fc1a158747 --- /dev/null +++ b/soh/assets/custom/objects/object_holiday_piece_2/gHolidayPiece2DL @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_holiday_piece_2/gHolidayPiece2DL_tri_0 b/soh/assets/custom/objects/object_holiday_piece_2/gHolidayPiece2DL_tri_0 new file mode 100644 index 00000000000..66ccbd69834 --- /dev/null +++ b/soh/assets/custom/objects/object_holiday_piece_2/gHolidayPiece2DL_tri_0 @@ -0,0 +1,67 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_holiday_piece_2/gHolidayPiece2DL_tri_1 b/soh/assets/custom/objects/object_holiday_piece_2/gHolidayPiece2DL_tri_1 new file mode 100644 index 00000000000..595dd9a28a5 --- /dev/null +++ b/soh/assets/custom/objects/object_holiday_piece_2/gHolidayPiece2DL_tri_1 @@ -0,0 +1,378 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_holiday_piece_2/gHolidayPiece2DL_tri_2 b/soh/assets/custom/objects/object_holiday_piece_2/gHolidayPiece2DL_tri_2 new file mode 100644 index 00000000000..6186a79e3df --- /dev/null +++ b/soh/assets/custom/objects/object_holiday_piece_2/gHolidayPiece2DL_tri_2 @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_holiday_piece_2/gHolidayPiece2DL_vtx_0 b/soh/assets/custom/objects/object_holiday_piece_2/gHolidayPiece2DL_vtx_0 new file mode 100644 index 00000000000..90dc38784f0 --- /dev/null +++ b/soh/assets/custom/objects/object_holiday_piece_2/gHolidayPiece2DL_vtx_0 @@ -0,0 +1,72 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_holiday_piece_2/gHolidayPiece2DL_vtx_1 b/soh/assets/custom/objects/object_holiday_piece_2/gHolidayPiece2DL_vtx_1 new file mode 100644 index 00000000000..ef4ea40e42a --- /dev/null +++ b/soh/assets/custom/objects/object_holiday_piece_2/gHolidayPiece2DL_vtx_1 @@ -0,0 +1,410 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_holiday_piece_2/gHolidayPiece2DL_vtx_2 b/soh/assets/custom/objects/object_holiday_piece_2/gHolidayPiece2DL_vtx_2 new file mode 100644 index 00000000000..ec4e737005b --- /dev/null +++ b/soh/assets/custom/objects/object_holiday_piece_2/gHolidayPiece2DL_vtx_2 @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_holiday_piece_2/image b/soh/assets/custom/objects/object_holiday_piece_2/image new file mode 100644 index 00000000000..2a8a62348be Binary files /dev/null and b/soh/assets/custom/objects/object_holiday_piece_2/image differ diff --git a/soh/assets/custom/objects/object_holiday_piece_2/image_copy b/soh/assets/custom/objects/object_holiday_piece_2/image_copy new file mode 100644 index 00000000000..2a8a62348be Binary files /dev/null and b/soh/assets/custom/objects/object_holiday_piece_2/image_copy differ diff --git a/soh/assets/custom/objects/object_holiday_piece_2/mat_gHolidayPiece2DL_Green_mat b/soh/assets/custom/objects/object_holiday_piece_2/mat_gHolidayPiece2DL_Green_mat new file mode 100644 index 00000000000..5f6f8bc6826 --- /dev/null +++ b/soh/assets/custom/objects/object_holiday_piece_2/mat_gHolidayPiece2DL_Green_mat @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_holiday_piece_2/mat_gHolidayPiece2DL_f3dlite_holiday_edges b/soh/assets/custom/objects/object_holiday_piece_2/mat_gHolidayPiece2DL_f3dlite_holiday_edges new file mode 100644 index 00000000000..0a6c2633a6c --- /dev/null +++ b/soh/assets/custom/objects/object_holiday_piece_2/mat_gHolidayPiece2DL_f3dlite_holiday_edges @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_holiday_piece_2/mat_gHolidayPiece2DL_f3dlite_holiday_surface b/soh/assets/custom/objects/object_holiday_piece_2/mat_gHolidayPiece2DL_f3dlite_holiday_surface new file mode 100644 index 00000000000..5619cd859ec --- /dev/null +++ b/soh/assets/custom/objects/object_holiday_piece_2/mat_gHolidayPiece2DL_f3dlite_holiday_surface @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_holiday_piece_2/mat_gHolidayPiece2DL_f3dlite_shard_edge b/soh/assets/custom/objects/object_holiday_piece_2/mat_gHolidayPiece2DL_f3dlite_shard_edge new file mode 100644 index 00000000000..c1580f98cac --- /dev/null +++ b/soh/assets/custom/objects/object_holiday_piece_2/mat_gHolidayPiece2DL_f3dlite_shard_edge @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_holiday_piece_2/mat_gHolidayPiece2DL_silver_001 b/soh/assets/custom/objects/object_holiday_piece_2/mat_gHolidayPiece2DL_silver_001 new file mode 100644 index 00000000000..c4798e6d5b7 --- /dev/null +++ b/soh/assets/custom/objects/object_holiday_piece_2/mat_gHolidayPiece2DL_silver_001 @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_holiday_piece_2/noise_tex b/soh/assets/custom/objects/object_holiday_piece_2/noise_tex new file mode 100644 index 00000000000..a6d6cf945e1 Binary files /dev/null and b/soh/assets/custom/objects/object_holiday_piece_2/noise_tex differ diff --git a/soh/assets/custom/objects/object_kakariko_decor/CuccoBedding_32 b/soh/assets/custom/objects/object_kakariko_decor/CuccoBedding_32 new file mode 100644 index 00000000000..68187fbf5f4 Binary files /dev/null and b/soh/assets/custom/objects/object_kakariko_decor/CuccoBedding_32 differ diff --git a/soh/assets/custom/objects/object_kakariko_decor/SnowBlanket_32 b/soh/assets/custom/objects/object_kakariko_decor/SnowBlanket_32 new file mode 100644 index 00000000000..733b3075735 Binary files /dev/null and b/soh/assets/custom/objects/object_kakariko_decor/SnowBlanket_32 differ diff --git a/soh/assets/custom/objects/object_kakariko_decor/SnowBuildup_32 b/soh/assets/custom/objects/object_kakariko_decor/SnowBuildup_32 new file mode 100644 index 00000000000..4653f07d12c Binary files /dev/null and b/soh/assets/custom/objects/object_kakariko_decor/SnowBuildup_32 differ diff --git a/soh/assets/custom/objects/object_kakariko_decor/SnowDissolve_32 b/soh/assets/custom/objects/object_kakariko_decor/SnowDissolve_32 new file mode 100644 index 00000000000..e7c7aa14292 Binary files /dev/null and b/soh/assets/custom/objects/object_kakariko_decor/SnowDissolve_32 differ diff --git a/soh/assets/custom/objects/object_kakariko_decor/String_BYBY_32 b/soh/assets/custom/objects/object_kakariko_decor/String_BYBY_32 new file mode 100644 index 00000000000..62efcb10a36 Binary files /dev/null and b/soh/assets/custom/objects/object_kakariko_decor/String_BYBY_32 differ diff --git a/soh/assets/custom/objects/object_kakariko_decor/String_GRGR_32 b/soh/assets/custom/objects/object_kakariko_decor/String_GRGR_32 new file mode 100644 index 00000000000..b905f21a9a6 Binary files /dev/null and b/soh/assets/custom/objects/object_kakariko_decor/String_GRGR_32 differ diff --git a/soh/assets/custom/objects/object_kakariko_decor/UsagiLeaf_8 b/soh/assets/custom/objects/object_kakariko_decor/UsagiLeaf_8 new file mode 100644 index 00000000000..fc4d3ff5be3 Binary files /dev/null and b/soh/assets/custom/objects/object_kakariko_decor/UsagiLeaf_8 differ diff --git a/soh/assets/custom/objects/object_kakariko_decor/YukiUsagi_16 b/soh/assets/custom/objects/object_kakariko_decor/YukiUsagi_16 new file mode 100644 index 00000000000..2034627ff95 Binary files /dev/null and b/soh/assets/custom/objects/object_kakariko_decor/YukiUsagi_16 differ diff --git a/soh/assets/custom/objects/object_kakariko_decor/gKakarikoAdultDecorDL b/soh/assets/custom/objects/object_kakariko_decor/gKakarikoAdultDecorDL new file mode 100644 index 00000000000..13d9447f76a --- /dev/null +++ b/soh/assets/custom/objects/object_kakariko_decor/gKakarikoAdultDecorDL @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_kakariko_decor/gKakarikoAdultDecorDL_tri_0 b/soh/assets/custom/objects/object_kakariko_decor/gKakarikoAdultDecorDL_tri_0 new file mode 100644 index 00000000000..1263127aa62 --- /dev/null +++ b/soh/assets/custom/objects/object_kakariko_decor/gKakarikoAdultDecorDL_tri_0 @@ -0,0 +1,7 @@ + + + + + + + diff --git a/soh/assets/custom/objects/object_kakariko_decor/gKakarikoAdultDecorDL_tri_1 b/soh/assets/custom/objects/object_kakariko_decor/gKakarikoAdultDecorDL_tri_1 new file mode 100644 index 00000000000..2aff9496e8e --- /dev/null +++ b/soh/assets/custom/objects/object_kakariko_decor/gKakarikoAdultDecorDL_tri_1 @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/soh/assets/custom/objects/object_kakariko_decor/gKakarikoAdultDecorDL_vtx_0 b/soh/assets/custom/objects/object_kakariko_decor/gKakarikoAdultDecorDL_vtx_0 new file mode 100644 index 00000000000..9f3683fed91 --- /dev/null +++ b/soh/assets/custom/objects/object_kakariko_decor/gKakarikoAdultDecorDL_vtx_0 @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_kakariko_decor/gKakarikoAdultDecorDL_vtx_1 b/soh/assets/custom/objects/object_kakariko_decor/gKakarikoAdultDecorDL_vtx_1 new file mode 100644 index 00000000000..e438e71cf2c --- /dev/null +++ b/soh/assets/custom/objects/object_kakariko_decor/gKakarikoAdultDecorDL_vtx_1 @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_kakariko_decor/gKakarikoAdultDecorDL_vtx_cull b/soh/assets/custom/objects/object_kakariko_decor/gKakarikoAdultDecorDL_vtx_cull new file mode 100644 index 00000000000..c3ecddb8a81 --- /dev/null +++ b/soh/assets/custom/objects/object_kakariko_decor/gKakarikoAdultDecorDL_vtx_cull @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_kakariko_decor/gKakarikoChildDecorDL b/soh/assets/custom/objects/object_kakariko_decor/gKakarikoChildDecorDL new file mode 100644 index 00000000000..a3972ce7a1f --- /dev/null +++ b/soh/assets/custom/objects/object_kakariko_decor/gKakarikoChildDecorDL @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_kakariko_decor/gKakarikoChildDecorDL_tri_0 b/soh/assets/custom/objects/object_kakariko_decor/gKakarikoChildDecorDL_tri_0 new file mode 100644 index 00000000000..eb7e92fdfce --- /dev/null +++ b/soh/assets/custom/objects/object_kakariko_decor/gKakarikoChildDecorDL_tri_0 @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_kakariko_decor/gKakarikoChildDecorDL_tri_1 b/soh/assets/custom/objects/object_kakariko_decor/gKakarikoChildDecorDL_tri_1 new file mode 100644 index 00000000000..77468a73821 --- /dev/null +++ b/soh/assets/custom/objects/object_kakariko_decor/gKakarikoChildDecorDL_tri_1 @@ -0,0 +1,73 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_kakariko_decor/gKakarikoChildDecorDL_tri_2 b/soh/assets/custom/objects/object_kakariko_decor/gKakarikoChildDecorDL_tri_2 new file mode 100644 index 00000000000..97634bb7bf9 --- /dev/null +++ b/soh/assets/custom/objects/object_kakariko_decor/gKakarikoChildDecorDL_tri_2 @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_kakariko_decor/gKakarikoChildDecorDL_vtx_0 b/soh/assets/custom/objects/object_kakariko_decor/gKakarikoChildDecorDL_vtx_0 new file mode 100644 index 00000000000..24e3e3d6eea --- /dev/null +++ b/soh/assets/custom/objects/object_kakariko_decor/gKakarikoChildDecorDL_vtx_0 @@ -0,0 +1,106 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_kakariko_decor/gKakarikoChildDecorDL_vtx_1 b/soh/assets/custom/objects/object_kakariko_decor/gKakarikoChildDecorDL_vtx_1 new file mode 100644 index 00000000000..0b12310ec50 --- /dev/null +++ b/soh/assets/custom/objects/object_kakariko_decor/gKakarikoChildDecorDL_vtx_1 @@ -0,0 +1,111 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_kakariko_decor/gKakarikoChildDecorDL_vtx_2 b/soh/assets/custom/objects/object_kakariko_decor/gKakarikoChildDecorDL_vtx_2 new file mode 100644 index 00000000000..f2a81069d0c --- /dev/null +++ b/soh/assets/custom/objects/object_kakariko_decor/gKakarikoChildDecorDL_vtx_2 @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_kakariko_decor/gKakarikoChildDecorDL_vtx_cull b/soh/assets/custom/objects/object_kakariko_decor/gKakarikoChildDecorDL_vtx_cull new file mode 100644 index 00000000000..b2e233783de --- /dev/null +++ b/soh/assets/custom/objects/object_kakariko_decor/gKakarikoChildDecorDL_vtx_cull @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_kakariko_decor/gKakarikoDecorDL b/soh/assets/custom/objects/object_kakariko_decor/gKakarikoDecorDL new file mode 100644 index 00000000000..f0363d242de --- /dev/null +++ b/soh/assets/custom/objects/object_kakariko_decor/gKakarikoDecorDL @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_kakariko_decor/gKakarikoDecorDL_tri_0 b/soh/assets/custom/objects/object_kakariko_decor/gKakarikoDecorDL_tri_0 new file mode 100644 index 00000000000..5ee2633e5f5 --- /dev/null +++ b/soh/assets/custom/objects/object_kakariko_decor/gKakarikoDecorDL_tri_0 @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_kakariko_decor/gKakarikoDecorDL_tri_1 b/soh/assets/custom/objects/object_kakariko_decor/gKakarikoDecorDL_tri_1 new file mode 100644 index 00000000000..2b52e5ef38c --- /dev/null +++ b/soh/assets/custom/objects/object_kakariko_decor/gKakarikoDecorDL_tri_1 @@ -0,0 +1,221 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_kakariko_decor/gKakarikoDecorDL_tri_2 b/soh/assets/custom/objects/object_kakariko_decor/gKakarikoDecorDL_tri_2 new file mode 100644 index 00000000000..d6f5dbafe5b --- /dev/null +++ b/soh/assets/custom/objects/object_kakariko_decor/gKakarikoDecorDL_tri_2 @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_kakariko_decor/gKakarikoDecorDL_tri_3 b/soh/assets/custom/objects/object_kakariko_decor/gKakarikoDecorDL_tri_3 new file mode 100644 index 00000000000..f66ba5914a0 --- /dev/null +++ b/soh/assets/custom/objects/object_kakariko_decor/gKakarikoDecorDL_tri_3 @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_kakariko_decor/gKakarikoDecorDL_tri_4 b/soh/assets/custom/objects/object_kakariko_decor/gKakarikoDecorDL_tri_4 new file mode 100644 index 00000000000..e0d3768970a --- /dev/null +++ b/soh/assets/custom/objects/object_kakariko_decor/gKakarikoDecorDL_tri_4 @@ -0,0 +1,6 @@ + + + + + + diff --git a/soh/assets/custom/objects/object_kakariko_decor/gKakarikoDecorDL_tri_5 b/soh/assets/custom/objects/object_kakariko_decor/gKakarikoDecorDL_tri_5 new file mode 100644 index 00000000000..ca52719c952 --- /dev/null +++ b/soh/assets/custom/objects/object_kakariko_decor/gKakarikoDecorDL_tri_5 @@ -0,0 +1,6 @@ + + + + + + diff --git a/soh/assets/custom/objects/object_kakariko_decor/gKakarikoDecorDL_tri_6 b/soh/assets/custom/objects/object_kakariko_decor/gKakarikoDecorDL_tri_6 new file mode 100644 index 00000000000..75be056f591 --- /dev/null +++ b/soh/assets/custom/objects/object_kakariko_decor/gKakarikoDecorDL_tri_6 @@ -0,0 +1,330 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_kakariko_decor/gKakarikoDecorDL_tri_7 b/soh/assets/custom/objects/object_kakariko_decor/gKakarikoDecorDL_tri_7 new file mode 100644 index 00000000000..eeedcf1ee5f --- /dev/null +++ b/soh/assets/custom/objects/object_kakariko_decor/gKakarikoDecorDL_tri_7 @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_kakariko_decor/gKakarikoDecorDL_vtx_0 b/soh/assets/custom/objects/object_kakariko_decor/gKakarikoDecorDL_vtx_0 new file mode 100644 index 00000000000..c0a07802c63 --- /dev/null +++ b/soh/assets/custom/objects/object_kakariko_decor/gKakarikoDecorDL_vtx_0 @@ -0,0 +1,106 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_kakariko_decor/gKakarikoDecorDL_vtx_1 b/soh/assets/custom/objects/object_kakariko_decor/gKakarikoDecorDL_vtx_1 new file mode 100644 index 00000000000..c7615dd3be2 --- /dev/null +++ b/soh/assets/custom/objects/object_kakariko_decor/gKakarikoDecorDL_vtx_1 @@ -0,0 +1,770 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_kakariko_decor/gKakarikoDecorDL_vtx_2 b/soh/assets/custom/objects/object_kakariko_decor/gKakarikoDecorDL_vtx_2 new file mode 100644 index 00000000000..3ff9d2e436b --- /dev/null +++ b/soh/assets/custom/objects/object_kakariko_decor/gKakarikoDecorDL_vtx_2 @@ -0,0 +1,82 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_kakariko_decor/gKakarikoDecorDL_vtx_3 b/soh/assets/custom/objects/object_kakariko_decor/gKakarikoDecorDL_vtx_3 new file mode 100644 index 00000000000..b0e49797ac6 --- /dev/null +++ b/soh/assets/custom/objects/object_kakariko_decor/gKakarikoDecorDL_vtx_3 @@ -0,0 +1,74 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_kakariko_decor/gKakarikoDecorDL_vtx_4 b/soh/assets/custom/objects/object_kakariko_decor/gKakarikoDecorDL_vtx_4 new file mode 100644 index 00000000000..0434529395c --- /dev/null +++ b/soh/assets/custom/objects/object_kakariko_decor/gKakarikoDecorDL_vtx_4 @@ -0,0 +1,6 @@ + + + + + + diff --git a/soh/assets/custom/objects/object_kakariko_decor/gKakarikoDecorDL_vtx_5 b/soh/assets/custom/objects/object_kakariko_decor/gKakarikoDecorDL_vtx_5 new file mode 100644 index 00000000000..46c6ac9a192 --- /dev/null +++ b/soh/assets/custom/objects/object_kakariko_decor/gKakarikoDecorDL_vtx_5 @@ -0,0 +1,6 @@ + + + + + + diff --git a/soh/assets/custom/objects/object_kakariko_decor/gKakarikoDecorDL_vtx_6 b/soh/assets/custom/objects/object_kakariko_decor/gKakarikoDecorDL_vtx_6 new file mode 100644 index 00000000000..0e597050551 --- /dev/null +++ b/soh/assets/custom/objects/object_kakariko_decor/gKakarikoDecorDL_vtx_6 @@ -0,0 +1,497 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_kakariko_decor/gKakarikoDecorDL_vtx_7 b/soh/assets/custom/objects/object_kakariko_decor/gKakarikoDecorDL_vtx_7 new file mode 100644 index 00000000000..1929d460ef2 --- /dev/null +++ b/soh/assets/custom/objects/object_kakariko_decor/gKakarikoDecorDL_vtx_7 @@ -0,0 +1,230 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_kakariko_decor/gKakarikoDecorDL_vtx_cull b/soh/assets/custom/objects/object_kakariko_decor/gKakarikoDecorDL_vtx_cull new file mode 100644 index 00000000000..a183da29360 --- /dev/null +++ b/soh/assets/custom/objects/object_kakariko_decor/gKakarikoDecorDL_vtx_cull @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_kakariko_decor/mat_gKakarikoAdultDecorDL_f3dlite_StringGRGR b/soh/assets/custom/objects/object_kakariko_decor/mat_gKakarikoAdultDecorDL_f3dlite_StringGRGR new file mode 100644 index 00000000000..b8478fa9025 --- /dev/null +++ b/soh/assets/custom/objects/object_kakariko_decor/mat_gKakarikoAdultDecorDL_f3dlite_StringGRGR @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_kakariko_decor/mat_gKakarikoAdultDecorDL_f3dlite_snowlayer b/soh/assets/custom/objects/object_kakariko_decor/mat_gKakarikoAdultDecorDL_f3dlite_snowlayer new file mode 100644 index 00000000000..5456b3893ac --- /dev/null +++ b/soh/assets/custom/objects/object_kakariko_decor/mat_gKakarikoAdultDecorDL_f3dlite_snowlayer @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_kakariko_decor/mat_gKakarikoChildDecorDL_f3dlite_SnowBuildup b/soh/assets/custom/objects/object_kakariko_decor/mat_gKakarikoChildDecorDL_f3dlite_SnowBuildup new file mode 100644 index 00000000000..a79cca54f3b --- /dev/null +++ b/soh/assets/custom/objects/object_kakariko_decor/mat_gKakarikoChildDecorDL_f3dlite_SnowBuildup @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_kakariko_decor/mat_gKakarikoChildDecorDL_f3dlite_UsagiLeaf b/soh/assets/custom/objects/object_kakariko_decor/mat_gKakarikoChildDecorDL_f3dlite_UsagiLeaf new file mode 100644 index 00000000000..be29498efe7 --- /dev/null +++ b/soh/assets/custom/objects/object_kakariko_decor/mat_gKakarikoChildDecorDL_f3dlite_UsagiLeaf @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_kakariko_decor/mat_gKakarikoChildDecorDL_f3dlite_YukiUsagiBody b/soh/assets/custom/objects/object_kakariko_decor/mat_gKakarikoChildDecorDL_f3dlite_YukiUsagiBody new file mode 100644 index 00000000000..3a2d2c88989 --- /dev/null +++ b/soh/assets/custom/objects/object_kakariko_decor/mat_gKakarikoChildDecorDL_f3dlite_YukiUsagiBody @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_kakariko_decor/mat_gKakarikoDecorDL_f3dlite_CuccoBedding b/soh/assets/custom/objects/object_kakariko_decor/mat_gKakarikoDecorDL_f3dlite_CuccoBedding new file mode 100644 index 00000000000..52025821097 --- /dev/null +++ b/soh/assets/custom/objects/object_kakariko_decor/mat_gKakarikoDecorDL_f3dlite_CuccoBedding @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_kakariko_decor/mat_gKakarikoDecorDL_f3dlite_SnowBuildup b/soh/assets/custom/objects/object_kakariko_decor/mat_gKakarikoDecorDL_f3dlite_SnowBuildup new file mode 100644 index 00000000000..a79cca54f3b --- /dev/null +++ b/soh/assets/custom/objects/object_kakariko_decor/mat_gKakarikoDecorDL_f3dlite_SnowBuildup @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_kakariko_decor/mat_gKakarikoDecorDL_f3dlite_StringBYBY b/soh/assets/custom/objects/object_kakariko_decor/mat_gKakarikoDecorDL_f3dlite_StringBYBY new file mode 100644 index 00000000000..5c85eb88f40 --- /dev/null +++ b/soh/assets/custom/objects/object_kakariko_decor/mat_gKakarikoDecorDL_f3dlite_StringBYBY @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_kakariko_decor/mat_gKakarikoDecorDL_f3dlite_StringGRGR b/soh/assets/custom/objects/object_kakariko_decor/mat_gKakarikoDecorDL_f3dlite_StringGRGR new file mode 100644 index 00000000000..b8478fa9025 --- /dev/null +++ b/soh/assets/custom/objects/object_kakariko_decor/mat_gKakarikoDecorDL_f3dlite_StringGRGR @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_kakariko_decor/mat_gKakarikoDecorDL_f3dlite_UsagiLeaf b/soh/assets/custom/objects/object_kakariko_decor/mat_gKakarikoDecorDL_f3dlite_UsagiLeaf new file mode 100644 index 00000000000..be29498efe7 --- /dev/null +++ b/soh/assets/custom/objects/object_kakariko_decor/mat_gKakarikoDecorDL_f3dlite_UsagiLeaf @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_kakariko_decor/mat_gKakarikoDecorDL_f3dlite_YukiUsagiBody b/soh/assets/custom/objects/object_kakariko_decor/mat_gKakarikoDecorDL_f3dlite_YukiUsagiBody new file mode 100644 index 00000000000..3a2d2c88989 --- /dev/null +++ b/soh/assets/custom/objects/object_kakariko_decor/mat_gKakarikoDecorDL_f3dlite_YukiUsagiBody @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_kakariko_decor/mat_gKakarikoDecorDL_f3dlite_snowdissolve b/soh/assets/custom/objects/object_kakariko_decor/mat_gKakarikoDecorDL_f3dlite_snowdissolve new file mode 100644 index 00000000000..e86142f2970 --- /dev/null +++ b/soh/assets/custom/objects/object_kakariko_decor/mat_gKakarikoDecorDL_f3dlite_snowdissolve @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_kakariko_decor/mat_gKakarikoDecorDL_f3dlite_snowlayer b/soh/assets/custom/objects/object_kakariko_decor/mat_gKakarikoDecorDL_f3dlite_snowlayer new file mode 100644 index 00000000000..5456b3893ac --- /dev/null +++ b/soh/assets/custom/objects/object_kakariko_decor/mat_gKakarikoDecorDL_f3dlite_snowlayer @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_mag/gTitleArchipelagoSubtitleTex.rgba32.png b/soh/assets/custom/objects/object_mag/gTitleArchipelagoSubtitleTex.rgba32.png new file mode 100644 index 00000000000..5e0b83446dc Binary files /dev/null and b/soh/assets/custom/objects/object_mag/gTitleArchipelagoSubtitleTex.rgba32.png differ diff --git a/soh/assets/custom/objects/object_mag/gTitleRogueLikeSubtitleTex.rgba32.png b/soh/assets/custom/objects/object_mag/gTitleRogueLikeSubtitleTex.rgba32.png new file mode 100644 index 00000000000..f64b6ba0c9a Binary files /dev/null and b/soh/assets/custom/objects/object_mag/gTitleRogueLikeSubtitleTex.rgba32.png differ diff --git a/soh/assets/custom/objects/object_penguin/mat_object_penguin_DL_beak b/soh/assets/custom/objects/object_penguin/mat_object_penguin_DL_beak new file mode 100644 index 00000000000..f83feec1215 --- /dev/null +++ b/soh/assets/custom/objects/object_penguin/mat_object_penguin_DL_beak @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_penguin/mat_object_penguin_DL_eye b/soh/assets/custom/objects/object_penguin/mat_object_penguin_DL_eye new file mode 100644 index 00000000000..c24abe2ab28 --- /dev/null +++ b/soh/assets/custom/objects/object_penguin/mat_object_penguin_DL_eye @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_penguin/mat_object_penguin_DL_skin b/soh/assets/custom/objects/object_penguin/mat_object_penguin_DL_skin new file mode 100644 index 00000000000..6c57863e3c2 --- /dev/null +++ b/soh/assets/custom/objects/object_penguin/mat_object_penguin_DL_skin @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_penguin/object_penguin_DL b/soh/assets/custom/objects/object_penguin/object_penguin_DL new file mode 100644 index 00000000000..393bbc4385f --- /dev/null +++ b/soh/assets/custom/objects/object_penguin/object_penguin_DL @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_penguin/object_penguin_DL_tri_0 b/soh/assets/custom/objects/object_penguin/object_penguin_DL_tri_0 new file mode 100644 index 00000000000..9039650d4ff --- /dev/null +++ b/soh/assets/custom/objects/object_penguin/object_penguin_DL_tri_0 @@ -0,0 +1,862 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_penguin/object_penguin_DL_tri_1 b/soh/assets/custom/objects/object_penguin/object_penguin_DL_tri_1 new file mode 100644 index 00000000000..efb326acefc --- /dev/null +++ b/soh/assets/custom/objects/object_penguin/object_penguin_DL_tri_1 @@ -0,0 +1,210 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_penguin/object_penguin_DL_tri_2 b/soh/assets/custom/objects/object_penguin/object_penguin_DL_tri_2 new file mode 100644 index 00000000000..96c7933ecc8 --- /dev/null +++ b/soh/assets/custom/objects/object_penguin/object_penguin_DL_tri_2 @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/soh/assets/custom/objects/object_penguin/object_penguin_DL_vtx_0 b/soh/assets/custom/objects/object_penguin/object_penguin_DL_vtx_0 new file mode 100644 index 00000000000..f1a650d477e --- /dev/null +++ b/soh/assets/custom/objects/object_penguin/object_penguin_DL_vtx_0 @@ -0,0 +1,922 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_penguin/object_penguin_DL_vtx_1 b/soh/assets/custom/objects/object_penguin/object_penguin_DL_vtx_1 new file mode 100644 index 00000000000..5cd519f955c --- /dev/null +++ b/soh/assets/custom/objects/object_penguin/object_penguin_DL_vtx_1 @@ -0,0 +1,215 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_penguin/object_penguin_DL_vtx_2 b/soh/assets/custom/objects/object_penguin/object_penguin_DL_vtx_2 new file mode 100644 index 00000000000..97f03bf0f41 --- /dev/null +++ b/soh/assets/custom/objects/object_penguin/object_penguin_DL_vtx_2 @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_penguin/ping_eye b/soh/assets/custom/objects/object_penguin/ping_eye new file mode 100644 index 00000000000..32eb2e7e10e Binary files /dev/null and b/soh/assets/custom/objects/object_penguin/ping_eye differ diff --git a/soh/assets/custom/objects/object_penguin/ping_tex b/soh/assets/custom/objects/object_penguin/ping_tex new file mode 100644 index 00000000000..9913decc7dd Binary files /dev/null and b/soh/assets/custom/objects/object_penguin/ping_tex differ diff --git a/soh/assets/custom/objects/object_temple_of_time_decor/SnowBlanket_32 b/soh/assets/custom/objects/object_temple_of_time_decor/SnowBlanket_32 new file mode 100644 index 00000000000..733b3075735 Binary files /dev/null and b/soh/assets/custom/objects/object_temple_of_time_decor/SnowBlanket_32 differ diff --git a/soh/assets/custom/objects/object_temple_of_time_decor/SnowBuildup_32 b/soh/assets/custom/objects/object_temple_of_time_decor/SnowBuildup_32 new file mode 100644 index 00000000000..4653f07d12c Binary files /dev/null and b/soh/assets/custom/objects/object_temple_of_time_decor/SnowBuildup_32 differ diff --git a/soh/assets/custom/objects/object_temple_of_time_decor/String_BYBY_32 b/soh/assets/custom/objects/object_temple_of_time_decor/String_BYBY_32 new file mode 100644 index 00000000000..62efcb10a36 Binary files /dev/null and b/soh/assets/custom/objects/object_temple_of_time_decor/String_BYBY_32 differ diff --git a/soh/assets/custom/objects/object_temple_of_time_decor/String_GRGR_32 b/soh/assets/custom/objects/object_temple_of_time_decor/String_GRGR_32 new file mode 100644 index 00000000000..b905f21a9a6 Binary files /dev/null and b/soh/assets/custom/objects/object_temple_of_time_decor/String_GRGR_32 differ diff --git a/soh/assets/custom/objects/object_temple_of_time_decor/gTempleOfTimeDecorDL b/soh/assets/custom/objects/object_temple_of_time_decor/gTempleOfTimeDecorDL new file mode 100644 index 00000000000..491663279db --- /dev/null +++ b/soh/assets/custom/objects/object_temple_of_time_decor/gTempleOfTimeDecorDL @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_temple_of_time_decor/gTempleOfTimeDecorDL_tri_0 b/soh/assets/custom/objects/object_temple_of_time_decor/gTempleOfTimeDecorDL_tri_0 new file mode 100644 index 00000000000..75fc6598b2c --- /dev/null +++ b/soh/assets/custom/objects/object_temple_of_time_decor/gTempleOfTimeDecorDL_tri_0 @@ -0,0 +1,2222 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_temple_of_time_decor/gTempleOfTimeDecorDL_tri_1 b/soh/assets/custom/objects/object_temple_of_time_decor/gTempleOfTimeDecorDL_tri_1 new file mode 100644 index 00000000000..bf36e26c42b --- /dev/null +++ b/soh/assets/custom/objects/object_temple_of_time_decor/gTempleOfTimeDecorDL_tri_1 @@ -0,0 +1,7 @@ + + + + + + + diff --git a/soh/assets/custom/objects/object_temple_of_time_decor/gTempleOfTimeDecorDL_tri_2 b/soh/assets/custom/objects/object_temple_of_time_decor/gTempleOfTimeDecorDL_tri_2 new file mode 100644 index 00000000000..4020ff3d1f0 --- /dev/null +++ b/soh/assets/custom/objects/object_temple_of_time_decor/gTempleOfTimeDecorDL_tri_2 @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_temple_of_time_decor/gTempleOfTimeDecorDL_tri_3 b/soh/assets/custom/objects/object_temple_of_time_decor/gTempleOfTimeDecorDL_tri_3 new file mode 100644 index 00000000000..eb072a5ef74 --- /dev/null +++ b/soh/assets/custom/objects/object_temple_of_time_decor/gTempleOfTimeDecorDL_tri_3 @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_temple_of_time_decor/gTempleOfTimeDecorDL_tri_4 b/soh/assets/custom/objects/object_temple_of_time_decor/gTempleOfTimeDecorDL_tri_4 new file mode 100644 index 00000000000..34fbdd80624 --- /dev/null +++ b/soh/assets/custom/objects/object_temple_of_time_decor/gTempleOfTimeDecorDL_tri_4 @@ -0,0 +1,69 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_temple_of_time_decor/gTempleOfTimeDecorDL_tri_5 b/soh/assets/custom/objects/object_temple_of_time_decor/gTempleOfTimeDecorDL_tri_5 new file mode 100644 index 00000000000..e360d5739ed --- /dev/null +++ b/soh/assets/custom/objects/object_temple_of_time_decor/gTempleOfTimeDecorDL_tri_5 @@ -0,0 +1,87 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_temple_of_time_decor/gTempleOfTimeDecorDL_tri_6 b/soh/assets/custom/objects/object_temple_of_time_decor/gTempleOfTimeDecorDL_tri_6 new file mode 100644 index 00000000000..8dcd07878ce --- /dev/null +++ b/soh/assets/custom/objects/object_temple_of_time_decor/gTempleOfTimeDecorDL_tri_6 @@ -0,0 +1,87 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_temple_of_time_decor/gTempleOfTimeDecorDL_tri_7 b/soh/assets/custom/objects/object_temple_of_time_decor/gTempleOfTimeDecorDL_tri_7 new file mode 100644 index 00000000000..a276056ed75 --- /dev/null +++ b/soh/assets/custom/objects/object_temple_of_time_decor/gTempleOfTimeDecorDL_tri_7 @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_temple_of_time_decor/gTempleOfTimeDecorDL_tri_8 b/soh/assets/custom/objects/object_temple_of_time_decor/gTempleOfTimeDecorDL_tri_8 new file mode 100644 index 00000000000..b7f90b048a6 --- /dev/null +++ b/soh/assets/custom/objects/object_temple_of_time_decor/gTempleOfTimeDecorDL_tri_8 @@ -0,0 +1,7 @@ + + + + + + + diff --git a/soh/assets/custom/objects/object_temple_of_time_decor/gTempleOfTimeDecorDL_vtx_0 b/soh/assets/custom/objects/object_temple_of_time_decor/gTempleOfTimeDecorDL_vtx_0 new file mode 100644 index 00000000000..772a7955d4c --- /dev/null +++ b/soh/assets/custom/objects/object_temple_of_time_decor/gTempleOfTimeDecorDL_vtx_0 @@ -0,0 +1,4468 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_temple_of_time_decor/gTempleOfTimeDecorDL_vtx_1 b/soh/assets/custom/objects/object_temple_of_time_decor/gTempleOfTimeDecorDL_vtx_1 new file mode 100644 index 00000000000..f62970ec9e0 --- /dev/null +++ b/soh/assets/custom/objects/object_temple_of_time_decor/gTempleOfTimeDecorDL_vtx_1 @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/soh/assets/custom/objects/object_temple_of_time_decor/gTempleOfTimeDecorDL_vtx_2 b/soh/assets/custom/objects/object_temple_of_time_decor/gTempleOfTimeDecorDL_vtx_2 new file mode 100644 index 00000000000..30da04c9f6e --- /dev/null +++ b/soh/assets/custom/objects/object_temple_of_time_decor/gTempleOfTimeDecorDL_vtx_2 @@ -0,0 +1,74 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_temple_of_time_decor/gTempleOfTimeDecorDL_vtx_3 b/soh/assets/custom/objects/object_temple_of_time_decor/gTempleOfTimeDecorDL_vtx_3 new file mode 100644 index 00000000000..834efd769c9 --- /dev/null +++ b/soh/assets/custom/objects/object_temple_of_time_decor/gTempleOfTimeDecorDL_vtx_3 @@ -0,0 +1,119 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_temple_of_time_decor/gTempleOfTimeDecorDL_vtx_4 b/soh/assets/custom/objects/object_temple_of_time_decor/gTempleOfTimeDecorDL_vtx_4 new file mode 100644 index 00000000000..acfe49fc65d --- /dev/null +++ b/soh/assets/custom/objects/object_temple_of_time_decor/gTempleOfTimeDecorDL_vtx_4 @@ -0,0 +1,224 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_temple_of_time_decor/gTempleOfTimeDecorDL_vtx_5 b/soh/assets/custom/objects/object_temple_of_time_decor/gTempleOfTimeDecorDL_vtx_5 new file mode 100644 index 00000000000..13fd6d48a69 --- /dev/null +++ b/soh/assets/custom/objects/object_temple_of_time_decor/gTempleOfTimeDecorDL_vtx_5 @@ -0,0 +1,284 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_temple_of_time_decor/gTempleOfTimeDecorDL_vtx_6 b/soh/assets/custom/objects/object_temple_of_time_decor/gTempleOfTimeDecorDL_vtx_6 new file mode 100644 index 00000000000..e8e4e4a528c --- /dev/null +++ b/soh/assets/custom/objects/object_temple_of_time_decor/gTempleOfTimeDecorDL_vtx_6 @@ -0,0 +1,278 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_temple_of_time_decor/gTempleOfTimeDecorDL_vtx_7 b/soh/assets/custom/objects/object_temple_of_time_decor/gTempleOfTimeDecorDL_vtx_7 new file mode 100644 index 00000000000..359d4871604 --- /dev/null +++ b/soh/assets/custom/objects/object_temple_of_time_decor/gTempleOfTimeDecorDL_vtx_7 @@ -0,0 +1,66 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_temple_of_time_decor/gTempleOfTimeDecorDL_vtx_8 b/soh/assets/custom/objects/object_temple_of_time_decor/gTempleOfTimeDecorDL_vtx_8 new file mode 100644 index 00000000000..2cb6bf701cc --- /dev/null +++ b/soh/assets/custom/objects/object_temple_of_time_decor/gTempleOfTimeDecorDL_vtx_8 @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_temple_of_time_decor/gTempleOfTimeDecorDL_vtx_cull b/soh/assets/custom/objects/object_temple_of_time_decor/gTempleOfTimeDecorDL_vtx_cull new file mode 100644 index 00000000000..6dd5d78cb9f --- /dev/null +++ b/soh/assets/custom/objects/object_temple_of_time_decor/gTempleOfTimeDecorDL_vtx_cull @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_temple_of_time_decor/ice b/soh/assets/custom/objects/object_temple_of_time_decor/ice new file mode 100644 index 00000000000..ff27a02466a Binary files /dev/null and b/soh/assets/custom/objects/object_temple_of_time_decor/ice differ diff --git a/soh/assets/custom/objects/object_temple_of_time_decor/mat_gTempleOfTimeDecorDL_f3dlite_SnowBuildup b/soh/assets/custom/objects/object_temple_of_time_decor/mat_gTempleOfTimeDecorDL_f3dlite_SnowBuildup new file mode 100644 index 00000000000..46d52d40d06 --- /dev/null +++ b/soh/assets/custom/objects/object_temple_of_time_decor/mat_gTempleOfTimeDecorDL_f3dlite_SnowBuildup @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_temple_of_time_decor/mat_gTempleOfTimeDecorDL_f3dlite_StringBYBY b/soh/assets/custom/objects/object_temple_of_time_decor/mat_gTempleOfTimeDecorDL_f3dlite_StringBYBY new file mode 100644 index 00000000000..16c505a28f7 --- /dev/null +++ b/soh/assets/custom/objects/object_temple_of_time_decor/mat_gTempleOfTimeDecorDL_f3dlite_StringBYBY @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_temple_of_time_decor/mat_gTempleOfTimeDecorDL_f3dlite_StringGRGR b/soh/assets/custom/objects/object_temple_of_time_decor/mat_gTempleOfTimeDecorDL_f3dlite_StringGRGR new file mode 100644 index 00000000000..5663994ee1d --- /dev/null +++ b/soh/assets/custom/objects/object_temple_of_time_decor/mat_gTempleOfTimeDecorDL_f3dlite_StringGRGR @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_temple_of_time_decor/mat_gTempleOfTimeDecorDL_f3dlite_gift_col_blue_material b/soh/assets/custom/objects/object_temple_of_time_decor/mat_gTempleOfTimeDecorDL_f3dlite_gift_col_blue_material new file mode 100644 index 00000000000..c0c1a4ac939 --- /dev/null +++ b/soh/assets/custom/objects/object_temple_of_time_decor/mat_gTempleOfTimeDecorDL_f3dlite_gift_col_blue_material @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_temple_of_time_decor/mat_gTempleOfTimeDecorDL_f3dlite_gift_col_green_material b/soh/assets/custom/objects/object_temple_of_time_decor/mat_gTempleOfTimeDecorDL_f3dlite_gift_col_green_material new file mode 100644 index 00000000000..6fcf6e1d8ff --- /dev/null +++ b/soh/assets/custom/objects/object_temple_of_time_decor/mat_gTempleOfTimeDecorDL_f3dlite_gift_col_green_material @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_temple_of_time_decor/mat_gTempleOfTimeDecorDL_f3dlite_gift_col_red_material b/soh/assets/custom/objects/object_temple_of_time_decor/mat_gTempleOfTimeDecorDL_f3dlite_gift_col_red_material new file mode 100644 index 00000000000..a73985478ca --- /dev/null +++ b/soh/assets/custom/objects/object_temple_of_time_decor/mat_gTempleOfTimeDecorDL_f3dlite_gift_col_red_material @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_temple_of_time_decor/mat_gTempleOfTimeDecorDL_f3dlite_gift_col_yellow_material b/soh/assets/custom/objects/object_temple_of_time_decor/mat_gTempleOfTimeDecorDL_f3dlite_gift_col_yellow_material new file mode 100644 index 00000000000..9f11354bdd6 --- /dev/null +++ b/soh/assets/custom/objects/object_temple_of_time_decor/mat_gTempleOfTimeDecorDL_f3dlite_gift_col_yellow_material @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_temple_of_time_decor/mat_gTempleOfTimeDecorDL_f3dlite_ice_material b/soh/assets/custom/objects/object_temple_of_time_decor/mat_gTempleOfTimeDecorDL_f3dlite_ice_material new file mode 100644 index 00000000000..2a71c2cc93b --- /dev/null +++ b/soh/assets/custom/objects/object_temple_of_time_decor/mat_gTempleOfTimeDecorDL_f3dlite_ice_material @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_temple_of_time_decor/mat_gTempleOfTimeDecorDL_f3dlite_snowlayer b/soh/assets/custom/objects/object_temple_of_time_decor/mat_gTempleOfTimeDecorDL_f3dlite_snowlayer new file mode 100644 index 00000000000..e15d18af101 --- /dev/null +++ b/soh/assets/custom/objects/object_temple_of_time_decor/mat_gTempleOfTimeDecorDL_f3dlite_snowlayer @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/Bark_64 b/soh/assets/custom/objects/object_xmas_tree/Bark_64 new file mode 100644 index 00000000000..8f1b837cd26 Binary files /dev/null and b/soh/assets/custom/objects/object_xmas_tree/Bark_64 differ diff --git a/soh/assets/custom/objects/object_xmas_tree/Bauble1Tex_B_32 b/soh/assets/custom/objects/object_xmas_tree/Bauble1Tex_B_32 new file mode 100644 index 00000000000..5e28d8e8203 Binary files /dev/null and b/soh/assets/custom/objects/object_xmas_tree/Bauble1Tex_B_32 differ diff --git a/soh/assets/custom/objects/object_xmas_tree/Bauble1Tex_G_32 b/soh/assets/custom/objects/object_xmas_tree/Bauble1Tex_G_32 new file mode 100644 index 00000000000..a40393dbd4a Binary files /dev/null and b/soh/assets/custom/objects/object_xmas_tree/Bauble1Tex_G_32 differ diff --git a/soh/assets/custom/objects/object_xmas_tree/Bauble1Tex_R_32 b/soh/assets/custom/objects/object_xmas_tree/Bauble1Tex_R_32 new file mode 100644 index 00000000000..c2d49ae4245 Binary files /dev/null and b/soh/assets/custom/objects/object_xmas_tree/Bauble1Tex_R_32 differ diff --git a/soh/assets/custom/objects/object_xmas_tree/Bauble2Tex_B_32 b/soh/assets/custom/objects/object_xmas_tree/Bauble2Tex_B_32 new file mode 100644 index 00000000000..2596d826432 Binary files /dev/null and b/soh/assets/custom/objects/object_xmas_tree/Bauble2Tex_B_32 differ diff --git a/soh/assets/custom/objects/object_xmas_tree/Bauble2Tex_G_32 b/soh/assets/custom/objects/object_xmas_tree/Bauble2Tex_G_32 new file mode 100644 index 00000000000..e6889fee4b0 Binary files /dev/null and b/soh/assets/custom/objects/object_xmas_tree/Bauble2Tex_G_32 differ diff --git a/soh/assets/custom/objects/object_xmas_tree/Bauble2Tex_R_32 b/soh/assets/custom/objects/object_xmas_tree/Bauble2Tex_R_32 new file mode 100644 index 00000000000..aef353a27ea Binary files /dev/null and b/soh/assets/custom/objects/object_xmas_tree/Bauble2Tex_R_32 differ diff --git a/soh/assets/custom/objects/object_xmas_tree/Bauble3Tex_B_32 b/soh/assets/custom/objects/object_xmas_tree/Bauble3Tex_B_32 new file mode 100644 index 00000000000..8f9c96cb13b Binary files /dev/null and b/soh/assets/custom/objects/object_xmas_tree/Bauble3Tex_B_32 differ diff --git a/soh/assets/custom/objects/object_xmas_tree/Bauble3Tex_G_32 b/soh/assets/custom/objects/object_xmas_tree/Bauble3Tex_G_32 new file mode 100644 index 00000000000..b8ce81e43fe Binary files /dev/null and b/soh/assets/custom/objects/object_xmas_tree/Bauble3Tex_G_32 differ diff --git a/soh/assets/custom/objects/object_xmas_tree/Bauble3Tex_R_32 b/soh/assets/custom/objects/object_xmas_tree/Bauble3Tex_R_32 new file mode 100644 index 00000000000..40394698fcd Binary files /dev/null and b/soh/assets/custom/objects/object_xmas_tree/Bauble3Tex_R_32 differ diff --git a/soh/assets/custom/objects/object_xmas_tree/GlowAlpha_64 b/soh/assets/custom/objects/object_xmas_tree/GlowAlpha_64 new file mode 100644 index 00000000000..1682d9e6f2f Binary files /dev/null and b/soh/assets/custom/objects/object_xmas_tree/GlowAlpha_64 differ diff --git a/soh/assets/custom/objects/object_xmas_tree/LeavesAlpha_64 b/soh/assets/custom/objects/object_xmas_tree/LeavesAlpha_64 new file mode 100644 index 00000000000..e33491d5af5 Binary files /dev/null and b/soh/assets/custom/objects/object_xmas_tree/LeavesAlpha_64 differ diff --git a/soh/assets/custom/objects/object_xmas_tree/LeavesAlpha_shaded_64 b/soh/assets/custom/objects/object_xmas_tree/LeavesAlpha_shaded_64 new file mode 100644 index 00000000000..dca2158b4b0 Binary files /dev/null and b/soh/assets/custom/objects/object_xmas_tree/LeavesAlpha_shaded_64 differ diff --git a/soh/assets/custom/objects/object_xmas_tree/Wrapping_B_32 b/soh/assets/custom/objects/object_xmas_tree/Wrapping_B_32 new file mode 100644 index 00000000000..9d70aa720ce Binary files /dev/null and b/soh/assets/custom/objects/object_xmas_tree/Wrapping_B_32 differ diff --git a/soh/assets/custom/objects/object_xmas_tree/Wrapping_B_64 b/soh/assets/custom/objects/object_xmas_tree/Wrapping_B_64 new file mode 100644 index 00000000000..071e01a2d14 Binary files /dev/null and b/soh/assets/custom/objects/object_xmas_tree/Wrapping_B_64 differ diff --git a/soh/assets/custom/objects/object_xmas_tree/Wrapping_G_32 b/soh/assets/custom/objects/object_xmas_tree/Wrapping_G_32 new file mode 100644 index 00000000000..8fac0c1c4fe Binary files /dev/null and b/soh/assets/custom/objects/object_xmas_tree/Wrapping_G_32 differ diff --git a/soh/assets/custom/objects/object_xmas_tree/Wrapping_G_64 b/soh/assets/custom/objects/object_xmas_tree/Wrapping_G_64 new file mode 100644 index 00000000000..6c128dbee7b Binary files /dev/null and b/soh/assets/custom/objects/object_xmas_tree/Wrapping_G_64 differ diff --git a/soh/assets/custom/objects/object_xmas_tree/Wrapping_R_32 b/soh/assets/custom/objects/object_xmas_tree/Wrapping_R_32 new file mode 100644 index 00000000000..a61f3d3867b Binary files /dev/null and b/soh/assets/custom/objects/object_xmas_tree/Wrapping_R_32 differ diff --git a/soh/assets/custom/objects/object_xmas_tree/Wrapping_R_64 b/soh/assets/custom/objects/object_xmas_tree/Wrapping_R_64 new file mode 100644 index 00000000000..d884c2ca670 Binary files /dev/null and b/soh/assets/custom/objects/object_xmas_tree/Wrapping_R_64 differ diff --git a/soh/assets/custom/objects/object_xmas_tree/gXmasDecor100DL b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor100DL new file mode 100644 index 00000000000..c354defdad6 --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor100DL @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/gXmasDecor100DL_tri_0 b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor100DL_tri_0 new file mode 100644 index 00000000000..bc1589a6573 --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor100DL_tri_0 @@ -0,0 +1,106 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/gXmasDecor100DL_tri_1 b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor100DL_tri_1 new file mode 100644 index 00000000000..ff14442dc0d --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor100DL_tri_1 @@ -0,0 +1,170 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/gXmasDecor100DL_tri_2 b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor100DL_tri_2 new file mode 100644 index 00000000000..ea5e79a96b1 --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor100DL_tri_2 @@ -0,0 +1,68 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/gXmasDecor100DL_tri_3 b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor100DL_tri_3 new file mode 100644 index 00000000000..4280e17a5d4 --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor100DL_tri_3 @@ -0,0 +1,68 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/gXmasDecor100DL_vtx_0 b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor100DL_vtx_0 new file mode 100644 index 00000000000..2d2b533a440 --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor100DL_vtx_0 @@ -0,0 +1,124 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/gXmasDecor100DL_vtx_1 b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor100DL_vtx_1 new file mode 100644 index 00000000000..2199d077963 --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor100DL_vtx_1 @@ -0,0 +1,220 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/gXmasDecor100DL_vtx_2 b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor100DL_vtx_2 new file mode 100644 index 00000000000..e0403545aa9 --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor100DL_vtx_2 @@ -0,0 +1,94 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/gXmasDecor100DL_vtx_3 b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor100DL_vtx_3 new file mode 100644 index 00000000000..ae1fba2946e --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor100DL_vtx_3 @@ -0,0 +1,94 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/gXmasDecor10DL b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor10DL new file mode 100644 index 00000000000..80f8fc81be3 --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor10DL @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/gXmasDecor10DL_tri_0 b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor10DL_tri_0 new file mode 100644 index 00000000000..2a2601403ad --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor10DL_tri_0 @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/gXmasDecor10DL_tri_1 b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor10DL_tri_1 new file mode 100644 index 00000000000..87dc5c7caf0 --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor10DL_tri_1 @@ -0,0 +1,7 @@ + + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/gXmasDecor10DL_tri_2 b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor10DL_tri_2 new file mode 100644 index 00000000000..d9d225ee153 --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor10DL_tri_2 @@ -0,0 +1,7 @@ + + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/gXmasDecor10DL_tri_3 b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor10DL_tri_3 new file mode 100644 index 00000000000..010dfe6698b --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor10DL_tri_3 @@ -0,0 +1,7 @@ + + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/gXmasDecor10DL_tri_4 b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor10DL_tri_4 new file mode 100644 index 00000000000..34de1a10f75 --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor10DL_tri_4 @@ -0,0 +1,7 @@ + + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/gXmasDecor10DL_tri_5 b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor10DL_tri_5 new file mode 100644 index 00000000000..dbaef4a37c6 --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor10DL_tri_5 @@ -0,0 +1,7 @@ + + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/gXmasDecor10DL_tri_6 b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor10DL_tri_6 new file mode 100644 index 00000000000..4b4aaa74244 --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor10DL_tri_6 @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/gXmasDecor10DL_tri_7 b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor10DL_tri_7 new file mode 100644 index 00000000000..a3aa6c67295 --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor10DL_tri_7 @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/gXmasDecor10DL_vtx_0 b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor10DL_vtx_0 new file mode 100644 index 00000000000..189a72f7836 --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor10DL_vtx_0 @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/gXmasDecor10DL_vtx_1 b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor10DL_vtx_1 new file mode 100644 index 00000000000..491afaa21aa --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor10DL_vtx_1 @@ -0,0 +1,6 @@ + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/gXmasDecor10DL_vtx_2 b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor10DL_vtx_2 new file mode 100644 index 00000000000..1c86dfb200f --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor10DL_vtx_2 @@ -0,0 +1,6 @@ + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/gXmasDecor10DL_vtx_3 b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor10DL_vtx_3 new file mode 100644 index 00000000000..fef6070e2c8 --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor10DL_vtx_3 @@ -0,0 +1,6 @@ + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/gXmasDecor10DL_vtx_4 b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor10DL_vtx_4 new file mode 100644 index 00000000000..74ff37e831f --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor10DL_vtx_4 @@ -0,0 +1,6 @@ + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/gXmasDecor10DL_vtx_5 b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor10DL_vtx_5 new file mode 100644 index 00000000000..e5347a692ec --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor10DL_vtx_5 @@ -0,0 +1,6 @@ + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/gXmasDecor10DL_vtx_6 b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor10DL_vtx_6 new file mode 100644 index 00000000000..57b0c83f5e3 --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor10DL_vtx_6 @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/gXmasDecor10DL_vtx_7 b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor10DL_vtx_7 new file mode 100644 index 00000000000..4eea9d31dc9 --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor10DL_vtx_7 @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/gXmasDecor20DL b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor20DL new file mode 100644 index 00000000000..cd83c25f822 --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor20DL @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/gXmasDecor20DL_tri_0 b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor20DL_tri_0 new file mode 100644 index 00000000000..7410becefa0 --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor20DL_tri_0 @@ -0,0 +1,106 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/gXmasDecor20DL_tri_1 b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor20DL_tri_1 new file mode 100644 index 00000000000..782f5f9e1db --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor20DL_tri_1 @@ -0,0 +1,68 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/gXmasDecor20DL_tri_2 b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor20DL_tri_2 new file mode 100644 index 00000000000..46d90cfee34 --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor20DL_tri_2 @@ -0,0 +1,197 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/gXmasDecor20DL_tri_3 b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor20DL_tri_3 new file mode 100644 index 00000000000..fcae1cb6087 --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor20DL_tri_3 @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/gXmasDecor20DL_tri_4 b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor20DL_tri_4 new file mode 100644 index 00000000000..cea2d4b0147 --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor20DL_tri_4 @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/gXmasDecor20DL_tri_5 b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor20DL_tri_5 new file mode 100644 index 00000000000..8e6c88ddf62 --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor20DL_tri_5 @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/gXmasDecor20DL_tri_6 b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor20DL_tri_6 new file mode 100644 index 00000000000..0c0928683c2 --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor20DL_tri_6 @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/gXmasDecor20DL_tri_7 b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor20DL_tri_7 new file mode 100644 index 00000000000..f564728104c --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor20DL_tri_7 @@ -0,0 +1,7 @@ + + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/gXmasDecor20DL_tri_8 b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor20DL_tri_8 new file mode 100644 index 00000000000..8464121eb12 --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor20DL_tri_8 @@ -0,0 +1,7 @@ + + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/gXmasDecor20DL_tri_9 b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor20DL_tri_9 new file mode 100644 index 00000000000..a22341bdf18 --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor20DL_tri_9 @@ -0,0 +1,7 @@ + + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/gXmasDecor20DL_vtx_0 b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor20DL_vtx_0 new file mode 100644 index 00000000000..c8a3028f56c --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor20DL_vtx_0 @@ -0,0 +1,124 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/gXmasDecor20DL_vtx_1 b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor20DL_vtx_1 new file mode 100644 index 00000000000..d7bf03045fe --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor20DL_vtx_1 @@ -0,0 +1,94 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/gXmasDecor20DL_vtx_2 b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor20DL_vtx_2 new file mode 100644 index 00000000000..768baea6575 --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor20DL_vtx_2 @@ -0,0 +1,300 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/gXmasDecor20DL_vtx_3 b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor20DL_vtx_3 new file mode 100644 index 00000000000..87d0b13154a --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor20DL_vtx_3 @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/gXmasDecor20DL_vtx_4 b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor20DL_vtx_4 new file mode 100644 index 00000000000..8fb01822419 --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor20DL_vtx_4 @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/gXmasDecor20DL_vtx_5 b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor20DL_vtx_5 new file mode 100644 index 00000000000..76d9b788af8 --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor20DL_vtx_5 @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/gXmasDecor20DL_vtx_6 b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor20DL_vtx_6 new file mode 100644 index 00000000000..60cef595cc3 --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor20DL_vtx_6 @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/gXmasDecor20DL_vtx_7 b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor20DL_vtx_7 new file mode 100644 index 00000000000..e590fd8487c --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor20DL_vtx_7 @@ -0,0 +1,6 @@ + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/gXmasDecor20DL_vtx_8 b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor20DL_vtx_8 new file mode 100644 index 00000000000..505f171b0da --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor20DL_vtx_8 @@ -0,0 +1,6 @@ + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/gXmasDecor20DL_vtx_9 b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor20DL_vtx_9 new file mode 100644 index 00000000000..50c131f9c9a --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor20DL_vtx_9 @@ -0,0 +1,6 @@ + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/gXmasDecor30DL b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor30DL new file mode 100644 index 00000000000..4e4fd95aad2 --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor30DL @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/gXmasDecor30DL_tri_0 b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor30DL_tri_0 new file mode 100644 index 00000000000..1ee54492c69 --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor30DL_tri_0 @@ -0,0 +1,7 @@ + + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/gXmasDecor30DL_tri_1 b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor30DL_tri_1 new file mode 100644 index 00000000000..0efacf68ad9 --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor30DL_tri_1 @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/gXmasDecor30DL_tri_2 b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor30DL_tri_2 new file mode 100644 index 00000000000..e05cc11fb6f --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor30DL_tri_2 @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/gXmasDecor30DL_tri_3 b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor30DL_tri_3 new file mode 100644 index 00000000000..19917bd2e89 --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor30DL_tri_3 @@ -0,0 +1,7 @@ + + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/gXmasDecor30DL_tri_4 b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor30DL_tri_4 new file mode 100644 index 00000000000..f5f256b23e1 --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor30DL_tri_4 @@ -0,0 +1,7 @@ + + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/gXmasDecor30DL_tri_5 b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor30DL_tri_5 new file mode 100644 index 00000000000..536d06f7acf --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor30DL_tri_5 @@ -0,0 +1,7 @@ + + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/gXmasDecor30DL_tri_6 b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor30DL_tri_6 new file mode 100644 index 00000000000..45568173a0d --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor30DL_tri_6 @@ -0,0 +1,7 @@ + + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/gXmasDecor30DL_vtx_0 b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor30DL_vtx_0 new file mode 100644 index 00000000000..509ed65291a --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor30DL_vtx_0 @@ -0,0 +1,6 @@ + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/gXmasDecor30DL_vtx_1 b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor30DL_vtx_1 new file mode 100644 index 00000000000..49c0a080a23 --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor30DL_vtx_1 @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/gXmasDecor30DL_vtx_2 b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor30DL_vtx_2 new file mode 100644 index 00000000000..c109c19f59f --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor30DL_vtx_2 @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/gXmasDecor30DL_vtx_3 b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor30DL_vtx_3 new file mode 100644 index 00000000000..c7c1e10a65e --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor30DL_vtx_3 @@ -0,0 +1,6 @@ + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/gXmasDecor30DL_vtx_4 b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor30DL_vtx_4 new file mode 100644 index 00000000000..a26e55df1d1 --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor30DL_vtx_4 @@ -0,0 +1,6 @@ + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/gXmasDecor30DL_vtx_5 b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor30DL_vtx_5 new file mode 100644 index 00000000000..d1177d63384 --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor30DL_vtx_5 @@ -0,0 +1,6 @@ + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/gXmasDecor30DL_vtx_6 b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor30DL_vtx_6 new file mode 100644 index 00000000000..d947223ad79 --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor30DL_vtx_6 @@ -0,0 +1,6 @@ + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/gXmasDecor40DL b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor40DL new file mode 100644 index 00000000000..9da99431f10 --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor40DL @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/gXmasDecor40DL_tri_0 b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor40DL_tri_0 new file mode 100644 index 00000000000..f94c732e4d9 --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor40DL_tri_0 @@ -0,0 +1,68 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/gXmasDecor40DL_tri_1 b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor40DL_tri_1 new file mode 100644 index 00000000000..34891928660 --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor40DL_tri_1 @@ -0,0 +1,68 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/gXmasDecor40DL_tri_10 b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor40DL_tri_10 new file mode 100644 index 00000000000..959b692d481 --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor40DL_tri_10 @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/gXmasDecor40DL_tri_11 b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor40DL_tri_11 new file mode 100644 index 00000000000..41fb8d2b9bb --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor40DL_tri_11 @@ -0,0 +1,7 @@ + + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/gXmasDecor40DL_tri_12 b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor40DL_tri_12 new file mode 100644 index 00000000000..8062b5a562c --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor40DL_tri_12 @@ -0,0 +1,7 @@ + + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/gXmasDecor40DL_tri_2 b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor40DL_tri_2 new file mode 100644 index 00000000000..7c52dae868b --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor40DL_tri_2 @@ -0,0 +1,68 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/gXmasDecor40DL_tri_3 b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor40DL_tri_3 new file mode 100644 index 00000000000..af789278611 --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor40DL_tri_3 @@ -0,0 +1,68 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/gXmasDecor40DL_tri_4 b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor40DL_tri_4 new file mode 100644 index 00000000000..e662f6ed6aa --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor40DL_tri_4 @@ -0,0 +1,68 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/gXmasDecor40DL_tri_5 b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor40DL_tri_5 new file mode 100644 index 00000000000..22b809b3f24 --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor40DL_tri_5 @@ -0,0 +1,7 @@ + + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/gXmasDecor40DL_tri_6 b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor40DL_tri_6 new file mode 100644 index 00000000000..1d845ad8a81 --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor40DL_tri_6 @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/gXmasDecor40DL_tri_7 b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor40DL_tri_7 new file mode 100644 index 00000000000..12c90c20934 --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor40DL_tri_7 @@ -0,0 +1,7 @@ + + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/gXmasDecor40DL_tri_8 b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor40DL_tri_8 new file mode 100644 index 00000000000..bbd9078cede --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor40DL_tri_8 @@ -0,0 +1,7 @@ + + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/gXmasDecor40DL_tri_9 b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor40DL_tri_9 new file mode 100644 index 00000000000..11e6780ea03 --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor40DL_tri_9 @@ -0,0 +1,7 @@ + + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/gXmasDecor40DL_vtx_0 b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor40DL_vtx_0 new file mode 100644 index 00000000000..8579d6fee95 --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor40DL_vtx_0 @@ -0,0 +1,94 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/gXmasDecor40DL_vtx_1 b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor40DL_vtx_1 new file mode 100644 index 00000000000..b0dcceeec23 --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor40DL_vtx_1 @@ -0,0 +1,94 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/gXmasDecor40DL_vtx_10 b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor40DL_vtx_10 new file mode 100644 index 00000000000..7d59969b3d7 --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor40DL_vtx_10 @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/gXmasDecor40DL_vtx_11 b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor40DL_vtx_11 new file mode 100644 index 00000000000..ab7d95f641a --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor40DL_vtx_11 @@ -0,0 +1,6 @@ + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/gXmasDecor40DL_vtx_12 b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor40DL_vtx_12 new file mode 100644 index 00000000000..514f00f3894 --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor40DL_vtx_12 @@ -0,0 +1,6 @@ + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/gXmasDecor40DL_vtx_2 b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor40DL_vtx_2 new file mode 100644 index 00000000000..7135901b7a4 --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor40DL_vtx_2 @@ -0,0 +1,94 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/gXmasDecor40DL_vtx_3 b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor40DL_vtx_3 new file mode 100644 index 00000000000..874e86e4556 --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor40DL_vtx_3 @@ -0,0 +1,94 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/gXmasDecor40DL_vtx_4 b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor40DL_vtx_4 new file mode 100644 index 00000000000..8dcece79bdc --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor40DL_vtx_4 @@ -0,0 +1,94 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/gXmasDecor40DL_vtx_5 b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor40DL_vtx_5 new file mode 100644 index 00000000000..030415bf5e1 --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor40DL_vtx_5 @@ -0,0 +1,6 @@ + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/gXmasDecor40DL_vtx_6 b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor40DL_vtx_6 new file mode 100644 index 00000000000..5758decfe91 --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor40DL_vtx_6 @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/gXmasDecor40DL_vtx_7 b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor40DL_vtx_7 new file mode 100644 index 00000000000..7e171bd29a8 --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor40DL_vtx_7 @@ -0,0 +1,6 @@ + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/gXmasDecor40DL_vtx_8 b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor40DL_vtx_8 new file mode 100644 index 00000000000..26f056a5fca --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor40DL_vtx_8 @@ -0,0 +1,6 @@ + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/gXmasDecor40DL_vtx_9 b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor40DL_vtx_9 new file mode 100644 index 00000000000..878f90b20ec --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor40DL_vtx_9 @@ -0,0 +1,6 @@ + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/gXmasDecor50DL b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor50DL new file mode 100644 index 00000000000..537d98d5f8f --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor50DL @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/gXmasDecor50DL_tri_0 b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor50DL_tri_0 new file mode 100644 index 00000000000..41e8cb3c97d --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor50DL_tri_0 @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/gXmasDecor50DL_tri_1 b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor50DL_tri_1 new file mode 100644 index 00000000000..0beb4cd5f4a --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor50DL_tri_1 @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/gXmasDecor50DL_tri_2 b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor50DL_tri_2 new file mode 100644 index 00000000000..a33dc968910 --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor50DL_tri_2 @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/gXmasDecor50DL_tri_3 b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor50DL_tri_3 new file mode 100644 index 00000000000..8cd67cdeb7d --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor50DL_tri_3 @@ -0,0 +1,7 @@ + + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/gXmasDecor50DL_tri_4 b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor50DL_tri_4 new file mode 100644 index 00000000000..f3cb40c18fd --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor50DL_tri_4 @@ -0,0 +1,7 @@ + + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/gXmasDecor50DL_tri_5 b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor50DL_tri_5 new file mode 100644 index 00000000000..28c1fa004f6 --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor50DL_tri_5 @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/gXmasDecor50DL_vtx_0 b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor50DL_vtx_0 new file mode 100644 index 00000000000..4bbea8de4d4 --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor50DL_vtx_0 @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/gXmasDecor50DL_vtx_1 b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor50DL_vtx_1 new file mode 100644 index 00000000000..c331cabed74 --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor50DL_vtx_1 @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/gXmasDecor50DL_vtx_2 b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor50DL_vtx_2 new file mode 100644 index 00000000000..62bb3dc536e --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor50DL_vtx_2 @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/gXmasDecor50DL_vtx_3 b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor50DL_vtx_3 new file mode 100644 index 00000000000..135764c05a0 --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor50DL_vtx_3 @@ -0,0 +1,6 @@ + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/gXmasDecor50DL_vtx_4 b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor50DL_vtx_4 new file mode 100644 index 00000000000..1066194fbf5 --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor50DL_vtx_4 @@ -0,0 +1,6 @@ + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/gXmasDecor50DL_vtx_5 b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor50DL_vtx_5 new file mode 100644 index 00000000000..afec2ee782a --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor50DL_vtx_5 @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/gXmasDecor60DL b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor60DL new file mode 100644 index 00000000000..fe51b9f6b84 --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor60DL @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/gXmasDecor60DL_tri_0 b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor60DL_tri_0 new file mode 100644 index 00000000000..1d746dde9cf --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor60DL_tri_0 @@ -0,0 +1,68 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/gXmasDecor60DL_tri_1 b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor60DL_tri_1 new file mode 100644 index 00000000000..095e5d8f1a3 --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor60DL_tri_1 @@ -0,0 +1,106 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/gXmasDecor60DL_tri_2 b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor60DL_tri_2 new file mode 100644 index 00000000000..b1f3c81fcaf --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor60DL_tri_2 @@ -0,0 +1,68 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/gXmasDecor60DL_tri_3 b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor60DL_tri_3 new file mode 100644 index 00000000000..e88a5cffe72 --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor60DL_tri_3 @@ -0,0 +1,133 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/gXmasDecor60DL_tri_4 b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor60DL_tri_4 new file mode 100644 index 00000000000..cbc9ce14a9c --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor60DL_tri_4 @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/gXmasDecor60DL_tri_5 b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor60DL_tri_5 new file mode 100644 index 00000000000..6646786c150 --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor60DL_tri_5 @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/gXmasDecor60DL_tri_6 b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor60DL_tri_6 new file mode 100644 index 00000000000..a2a881763ed --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor60DL_tri_6 @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/gXmasDecor60DL_tri_7 b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor60DL_tri_7 new file mode 100644 index 00000000000..2a5440b8677 --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor60DL_tri_7 @@ -0,0 +1,7 @@ + + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/gXmasDecor60DL_tri_8 b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor60DL_tri_8 new file mode 100644 index 00000000000..245daae2b22 --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor60DL_tri_8 @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/gXmasDecor60DL_tri_9 b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor60DL_tri_9 new file mode 100644 index 00000000000..34a3d0b55ce --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor60DL_tri_9 @@ -0,0 +1,7 @@ + + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/gXmasDecor60DL_vtx_0 b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor60DL_vtx_0 new file mode 100644 index 00000000000..b33405eec8f --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor60DL_vtx_0 @@ -0,0 +1,94 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/gXmasDecor60DL_vtx_1 b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor60DL_vtx_1 new file mode 100644 index 00000000000..ff671566d99 --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor60DL_vtx_1 @@ -0,0 +1,124 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/gXmasDecor60DL_vtx_2 b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor60DL_vtx_2 new file mode 100644 index 00000000000..21358026ec7 --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor60DL_vtx_2 @@ -0,0 +1,94 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/gXmasDecor60DL_vtx_3 b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor60DL_vtx_3 new file mode 100644 index 00000000000..831547972d3 --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor60DL_vtx_3 @@ -0,0 +1,202 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/gXmasDecor60DL_vtx_4 b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor60DL_vtx_4 new file mode 100644 index 00000000000..c591133b836 --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor60DL_vtx_4 @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/gXmasDecor60DL_vtx_5 b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor60DL_vtx_5 new file mode 100644 index 00000000000..d111addadae --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor60DL_vtx_5 @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/gXmasDecor60DL_vtx_6 b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor60DL_vtx_6 new file mode 100644 index 00000000000..2684a5759c5 --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor60DL_vtx_6 @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/gXmasDecor60DL_vtx_7 b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor60DL_vtx_7 new file mode 100644 index 00000000000..a2ab6b20a93 --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor60DL_vtx_7 @@ -0,0 +1,6 @@ + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/gXmasDecor60DL_vtx_8 b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor60DL_vtx_8 new file mode 100644 index 00000000000..fed438e57b0 --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor60DL_vtx_8 @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/gXmasDecor60DL_vtx_9 b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor60DL_vtx_9 new file mode 100644 index 00000000000..9f8e178ed21 --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor60DL_vtx_9 @@ -0,0 +1,6 @@ + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/gXmasDecor70DL b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor70DL new file mode 100644 index 00000000000..9d7a0b7491e --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor70DL @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/gXmasDecor70DL_tri_0 b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor70DL_tri_0 new file mode 100644 index 00000000000..540a5e3e395 --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor70DL_tri_0 @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/gXmasDecor70DL_tri_1 b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor70DL_tri_1 new file mode 100644 index 00000000000..3b27e35d234 --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor70DL_tri_1 @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/gXmasDecor70DL_tri_2 b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor70DL_tri_2 new file mode 100644 index 00000000000..29326efac0a --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor70DL_tri_2 @@ -0,0 +1,7 @@ + + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/gXmasDecor70DL_tri_3 b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor70DL_tri_3 new file mode 100644 index 00000000000..83850278e12 --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor70DL_tri_3 @@ -0,0 +1,7 @@ + + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/gXmasDecor70DL_tri_4 b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor70DL_tri_4 new file mode 100644 index 00000000000..38401ac4f3c --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor70DL_tri_4 @@ -0,0 +1,7 @@ + + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/gXmasDecor70DL_tri_5 b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor70DL_tri_5 new file mode 100644 index 00000000000..ef0b4397d75 --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor70DL_tri_5 @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/gXmasDecor70DL_tri_6 b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor70DL_tri_6 new file mode 100644 index 00000000000..0df60862536 --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor70DL_tri_6 @@ -0,0 +1,7 @@ + + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/gXmasDecor70DL_vtx_0 b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor70DL_vtx_0 new file mode 100644 index 00000000000..6e115c0bb30 --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor70DL_vtx_0 @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/gXmasDecor70DL_vtx_1 b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor70DL_vtx_1 new file mode 100644 index 00000000000..ce6941dc97c --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor70DL_vtx_1 @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/gXmasDecor70DL_vtx_2 b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor70DL_vtx_2 new file mode 100644 index 00000000000..56624ffb043 --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor70DL_vtx_2 @@ -0,0 +1,6 @@ + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/gXmasDecor70DL_vtx_3 b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor70DL_vtx_3 new file mode 100644 index 00000000000..ec758ec9199 --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor70DL_vtx_3 @@ -0,0 +1,6 @@ + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/gXmasDecor70DL_vtx_4 b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor70DL_vtx_4 new file mode 100644 index 00000000000..3e9cc0ff4cc --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor70DL_vtx_4 @@ -0,0 +1,6 @@ + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/gXmasDecor70DL_vtx_5 b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor70DL_vtx_5 new file mode 100644 index 00000000000..ac28e8fb0d9 --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor70DL_vtx_5 @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/gXmasDecor70DL_vtx_6 b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor70DL_vtx_6 new file mode 100644 index 00000000000..0cf3101e0f0 --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor70DL_vtx_6 @@ -0,0 +1,6 @@ + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/gXmasDecor80DL b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor80DL new file mode 100644 index 00000000000..ba14f553d07 --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor80DL @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/gXmasDecor80DL_tri_0 b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor80DL_tri_0 new file mode 100644 index 00000000000..233c5e5b743 --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor80DL_tri_0 @@ -0,0 +1,133 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/gXmasDecor80DL_tri_1 b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor80DL_tri_1 new file mode 100644 index 00000000000..160f85e3392 --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor80DL_tri_1 @@ -0,0 +1,68 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/gXmasDecor80DL_tri_2 b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor80DL_tri_2 new file mode 100644 index 00000000000..712db2eadf4 --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor80DL_tri_2 @@ -0,0 +1,133 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/gXmasDecor80DL_tri_3 b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor80DL_tri_3 new file mode 100644 index 00000000000..567f9bbfa63 --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor80DL_tri_3 @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/gXmasDecor80DL_tri_4 b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor80DL_tri_4 new file mode 100644 index 00000000000..ed60abbb97a --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor80DL_tri_4 @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/gXmasDecor80DL_tri_5 b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor80DL_tri_5 new file mode 100644 index 00000000000..9d012597673 --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor80DL_tri_5 @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/gXmasDecor80DL_tri_6 b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor80DL_tri_6 new file mode 100644 index 00000000000..011391daaf4 --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor80DL_tri_6 @@ -0,0 +1,7 @@ + + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/gXmasDecor80DL_tri_7 b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor80DL_tri_7 new file mode 100644 index 00000000000..ae4be6ebdd1 --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor80DL_tri_7 @@ -0,0 +1,7 @@ + + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/gXmasDecor80DL_tri_8 b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor80DL_tri_8 new file mode 100644 index 00000000000..b7a34eb1de0 --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor80DL_tri_8 @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/gXmasDecor80DL_vtx_0 b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor80DL_vtx_0 new file mode 100644 index 00000000000..4e538463f3a --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor80DL_vtx_0 @@ -0,0 +1,202 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/gXmasDecor80DL_vtx_1 b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor80DL_vtx_1 new file mode 100644 index 00000000000..1c7a6b49c92 --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor80DL_vtx_1 @@ -0,0 +1,94 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/gXmasDecor80DL_vtx_2 b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor80DL_vtx_2 new file mode 100644 index 00000000000..2717d84f0bc --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor80DL_vtx_2 @@ -0,0 +1,202 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/gXmasDecor80DL_vtx_3 b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor80DL_vtx_3 new file mode 100644 index 00000000000..e5f479fc47c --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor80DL_vtx_3 @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/gXmasDecor80DL_vtx_4 b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor80DL_vtx_4 new file mode 100644 index 00000000000..5e1500678a0 --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor80DL_vtx_4 @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/gXmasDecor80DL_vtx_5 b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor80DL_vtx_5 new file mode 100644 index 00000000000..e0036208883 --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor80DL_vtx_5 @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/gXmasDecor80DL_vtx_6 b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor80DL_vtx_6 new file mode 100644 index 00000000000..e67313e14f8 --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor80DL_vtx_6 @@ -0,0 +1,6 @@ + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/gXmasDecor80DL_vtx_7 b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor80DL_vtx_7 new file mode 100644 index 00000000000..7acc51f2a0c --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor80DL_vtx_7 @@ -0,0 +1,6 @@ + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/gXmasDecor80DL_vtx_8 b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor80DL_vtx_8 new file mode 100644 index 00000000000..c3ac2ef3648 --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor80DL_vtx_8 @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/gXmasDecor90DL b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor90DL new file mode 100644 index 00000000000..f96deccc4d7 --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor90DL @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/gXmasDecor90DL_tri_0 b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor90DL_tri_0 new file mode 100644 index 00000000000..aaf575f3edc --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor90DL_tri_0 @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/gXmasDecor90DL_tri_1 b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor90DL_tri_1 new file mode 100644 index 00000000000..17e2bbb1443 --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor90DL_tri_1 @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/gXmasDecor90DL_tri_2 b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor90DL_tri_2 new file mode 100644 index 00000000000..45f70fca8ef --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor90DL_tri_2 @@ -0,0 +1,7 @@ + + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/gXmasDecor90DL_tri_3 b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor90DL_tri_3 new file mode 100644 index 00000000000..501540e9913 --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor90DL_tri_3 @@ -0,0 +1,7 @@ + + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/gXmasDecor90DL_tri_4 b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor90DL_tri_4 new file mode 100644 index 00000000000..6aa20edebd3 --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor90DL_tri_4 @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/gXmasDecor90DL_tri_5 b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor90DL_tri_5 new file mode 100644 index 00000000000..9f5515c3b6a --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor90DL_tri_5 @@ -0,0 +1,7 @@ + + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/gXmasDecor90DL_tri_6 b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor90DL_tri_6 new file mode 100644 index 00000000000..e8b2ebe155f --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor90DL_tri_6 @@ -0,0 +1,7 @@ + + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/gXmasDecor90DL_tri_7 b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor90DL_tri_7 new file mode 100644 index 00000000000..e8686cfeb9a --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor90DL_tri_7 @@ -0,0 +1,7 @@ + + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/gXmasDecor90DL_vtx_0 b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor90DL_vtx_0 new file mode 100644 index 00000000000..4bf94f75a49 --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor90DL_vtx_0 @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/gXmasDecor90DL_vtx_1 b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor90DL_vtx_1 new file mode 100644 index 00000000000..a3e163984cf --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor90DL_vtx_1 @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/gXmasDecor90DL_vtx_2 b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor90DL_vtx_2 new file mode 100644 index 00000000000..5075a97320f --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor90DL_vtx_2 @@ -0,0 +1,6 @@ + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/gXmasDecor90DL_vtx_3 b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor90DL_vtx_3 new file mode 100644 index 00000000000..7b0fce1b261 --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor90DL_vtx_3 @@ -0,0 +1,6 @@ + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/gXmasDecor90DL_vtx_4 b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor90DL_vtx_4 new file mode 100644 index 00000000000..240d7afe250 --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor90DL_vtx_4 @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/gXmasDecor90DL_vtx_5 b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor90DL_vtx_5 new file mode 100644 index 00000000000..adf5dfa9b04 --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor90DL_vtx_5 @@ -0,0 +1,6 @@ + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/gXmasDecor90DL_vtx_6 b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor90DL_vtx_6 new file mode 100644 index 00000000000..5e02e04d8d4 --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor90DL_vtx_6 @@ -0,0 +1,6 @@ + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/gXmasDecor90DL_vtx_7 b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor90DL_vtx_7 new file mode 100644 index 00000000000..f92e3d98df2 --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/gXmasDecor90DL_vtx_7 @@ -0,0 +1,6 @@ + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/gXmasStarDL b/soh/assets/custom/objects/object_xmas_tree/gXmasStarDL new file mode 100644 index 00000000000..2bad08d2da6 --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/gXmasStarDL @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/gXmasStarDL_tri_0 b/soh/assets/custom/objects/object_xmas_tree/gXmasStarDL_tri_0 new file mode 100644 index 00000000000..79b9903f80a --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/gXmasStarDL_tri_0 @@ -0,0 +1,7 @@ + + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/gXmasStarDL_tri_1 b/soh/assets/custom/objects/object_xmas_tree/gXmasStarDL_tri_1 new file mode 100644 index 00000000000..0f82cf89324 --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/gXmasStarDL_tri_1 @@ -0,0 +1,390 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/gXmasStarDL_vtx_0 b/soh/assets/custom/objects/object_xmas_tree/gXmasStarDL_vtx_0 new file mode 100644 index 00000000000..a990fc5af05 --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/gXmasStarDL_vtx_0 @@ -0,0 +1,6 @@ + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/gXmasStarDL_vtx_1 b/soh/assets/custom/objects/object_xmas_tree/gXmasStarDL_vtx_1 new file mode 100644 index 00000000000..7467705946b --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/gXmasStarDL_vtx_1 @@ -0,0 +1,347 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/gXmasTreeDL b/soh/assets/custom/objects/object_xmas_tree/gXmasTreeDL new file mode 100644 index 00000000000..c343cfe2a0c --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/gXmasTreeDL @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/gXmasTreeDL_tri_0 b/soh/assets/custom/objects/object_xmas_tree/gXmasTreeDL_tri_0 new file mode 100644 index 00000000000..38cdbe014bb --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/gXmasTreeDL_tri_0 @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/gXmasTreeDL_tri_1 b/soh/assets/custom/objects/object_xmas_tree/gXmasTreeDL_tri_1 new file mode 100644 index 00000000000..ff4e066a5b1 --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/gXmasTreeDL_tri_1 @@ -0,0 +1,133 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/gXmasTreeDL_tri_2 b/soh/assets/custom/objects/object_xmas_tree/gXmasTreeDL_tri_2 new file mode 100644 index 00000000000..561d3ac5efb --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/gXmasTreeDL_tri_2 @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/gXmasTreeDL_vtx_0 b/soh/assets/custom/objects/object_xmas_tree/gXmasTreeDL_vtx_0 new file mode 100644 index 00000000000..94e5e1fefc0 --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/gXmasTreeDL_vtx_0 @@ -0,0 +1,66 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/gXmasTreeDL_vtx_1 b/soh/assets/custom/objects/object_xmas_tree/gXmasTreeDL_vtx_1 new file mode 100644 index 00000000000..01ed88a1458 --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/gXmasTreeDL_vtx_1 @@ -0,0 +1,146 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/gXmasTreeDL_vtx_2 b/soh/assets/custom/objects/object_xmas_tree/gXmasTreeDL_vtx_2 new file mode 100644 index 00000000000..fd03778f89a --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/gXmasTreeDL_vtx_2 @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/hilite_melon b/soh/assets/custom/objects/object_xmas_tree/hilite_melon new file mode 100644 index 00000000000..df1d605b38e Binary files /dev/null and b/soh/assets/custom/objects/object_xmas_tree/hilite_melon differ diff --git a/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor100DL_f3dlite_Wrapping_B_64 b/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor100DL_f3dlite_Wrapping_B_64 new file mode 100644 index 00000000000..e2cb1c6af43 --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor100DL_f3dlite_Wrapping_B_64 @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor100DL_f3dlite_Wrapping_G_32 b/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor100DL_f3dlite_Wrapping_G_32 new file mode 100644 index 00000000000..5f67d4de108 --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor100DL_f3dlite_Wrapping_G_32 @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor100DL_f3dlite_Wrapping_R_32 b/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor100DL_f3dlite_Wrapping_R_32 new file mode 100644 index 00000000000..f330997c280 --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor100DL_f3dlite_Wrapping_R_32 @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor100DL_f3dlite_Wrapping_R_64 b/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor100DL_f3dlite_Wrapping_R_64 new file mode 100644 index 00000000000..ddba85e48a7 --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor100DL_f3dlite_Wrapping_R_64 @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor10DL_f3dlite_Bauble1_B b/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor10DL_f3dlite_Bauble1_B new file mode 100644 index 00000000000..0562ad51423 --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor10DL_f3dlite_Bauble1_B @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor10DL_f3dlite_Bauble1_G b/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor10DL_f3dlite_Bauble1_G new file mode 100644 index 00000000000..424720964de --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor10DL_f3dlite_Bauble1_G @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor10DL_f3dlite_Bauble1_R b/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor10DL_f3dlite_Bauble1_R new file mode 100644 index 00000000000..ed8997bac1c --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor10DL_f3dlite_Bauble1_R @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor10DL_f3dlite_Bauble2_B b/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor10DL_f3dlite_Bauble2_B new file mode 100644 index 00000000000..a2fb866622d --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor10DL_f3dlite_Bauble2_B @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor10DL_f3dlite_Bauble2_G b/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor10DL_f3dlite_Bauble2_G new file mode 100644 index 00000000000..a3ebffce584 --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor10DL_f3dlite_Bauble2_G @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor10DL_f3dlite_Bauble2_R b/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor10DL_f3dlite_Bauble2_R new file mode 100644 index 00000000000..780bee1c538 --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor10DL_f3dlite_Bauble2_R @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor10DL_f3dlite_Bauble3_G b/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor10DL_f3dlite_Bauble3_G new file mode 100644 index 00000000000..5ec29067d2d --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor10DL_f3dlite_Bauble3_G @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor10DL_f3dlite_Bauble3_R b/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor10DL_f3dlite_Bauble3_R new file mode 100644 index 00000000000..6bfad5f53ee --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor10DL_f3dlite_Bauble3_R @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor20DL_f3dlite_Bauble1_B b/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor20DL_f3dlite_Bauble1_B new file mode 100644 index 00000000000..0562ad51423 --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor20DL_f3dlite_Bauble1_B @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor20DL_f3dlite_Bauble1_R b/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor20DL_f3dlite_Bauble1_R new file mode 100644 index 00000000000..ed8997bac1c --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor20DL_f3dlite_Bauble1_R @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor20DL_f3dlite_Bauble2_B b/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor20DL_f3dlite_Bauble2_B new file mode 100644 index 00000000000..a2fb866622d --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor20DL_f3dlite_Bauble2_B @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor20DL_f3dlite_Bauble2_G b/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor20DL_f3dlite_Bauble2_G new file mode 100644 index 00000000000..a3ebffce584 --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor20DL_f3dlite_Bauble2_G @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor20DL_f3dlite_Bauble2_R b/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor20DL_f3dlite_Bauble2_R new file mode 100644 index 00000000000..780bee1c538 --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor20DL_f3dlite_Bauble2_R @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor20DL_f3dlite_Bauble3_B b/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor20DL_f3dlite_Bauble3_B new file mode 100644 index 00000000000..7af3c0fdfa9 --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor20DL_f3dlite_Bauble3_B @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor20DL_f3dlite_Bauble3_G b/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor20DL_f3dlite_Bauble3_G new file mode 100644 index 00000000000..5ec29067d2d --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor20DL_f3dlite_Bauble3_G @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor20DL_f3dlite_Wrapping_B_32 b/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor20DL_f3dlite_Wrapping_B_32 new file mode 100644 index 00000000000..1425245e052 --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor20DL_f3dlite_Wrapping_B_32 @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor20DL_f3dlite_Wrapping_G_64 b/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor20DL_f3dlite_Wrapping_G_64 new file mode 100644 index 00000000000..c3a973f3929 --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor20DL_f3dlite_Wrapping_G_64 @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor20DL_f3dlite_Wrapping_R_32 b/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor20DL_f3dlite_Wrapping_R_32 new file mode 100644 index 00000000000..f330997c280 --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor20DL_f3dlite_Wrapping_R_32 @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor30DL_f3dlite_Bauble1_B b/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor30DL_f3dlite_Bauble1_B new file mode 100644 index 00000000000..0562ad51423 --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor30DL_f3dlite_Bauble1_B @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor30DL_f3dlite_Bauble1_G b/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor30DL_f3dlite_Bauble1_G new file mode 100644 index 00000000000..424720964de --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor30DL_f3dlite_Bauble1_G @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor30DL_f3dlite_Bauble1_R b/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor30DL_f3dlite_Bauble1_R new file mode 100644 index 00000000000..ed8997bac1c --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor30DL_f3dlite_Bauble1_R @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor30DL_f3dlite_Bauble2_B b/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor30DL_f3dlite_Bauble2_B new file mode 100644 index 00000000000..a2fb866622d --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor30DL_f3dlite_Bauble2_B @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor30DL_f3dlite_Bauble2_G b/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor30DL_f3dlite_Bauble2_G new file mode 100644 index 00000000000..a3ebffce584 --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor30DL_f3dlite_Bauble2_G @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor30DL_f3dlite_Bauble3_B b/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor30DL_f3dlite_Bauble3_B new file mode 100644 index 00000000000..7af3c0fdfa9 --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor30DL_f3dlite_Bauble3_B @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor30DL_f3dlite_Bauble3_R b/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor30DL_f3dlite_Bauble3_R new file mode 100644 index 00000000000..6bfad5f53ee --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor30DL_f3dlite_Bauble3_R @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor40DL_f3dlite_Bauble1_B b/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor40DL_f3dlite_Bauble1_B new file mode 100644 index 00000000000..0562ad51423 --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor40DL_f3dlite_Bauble1_B @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor40DL_f3dlite_Bauble1_R b/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor40DL_f3dlite_Bauble1_R new file mode 100644 index 00000000000..ed8997bac1c --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor40DL_f3dlite_Bauble1_R @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor40DL_f3dlite_Bauble2_B b/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor40DL_f3dlite_Bauble2_B new file mode 100644 index 00000000000..a2fb866622d --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor40DL_f3dlite_Bauble2_B @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor40DL_f3dlite_Bauble2_G b/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor40DL_f3dlite_Bauble2_G new file mode 100644 index 00000000000..a3ebffce584 --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor40DL_f3dlite_Bauble2_G @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor40DL_f3dlite_Bauble2_R b/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor40DL_f3dlite_Bauble2_R new file mode 100644 index 00000000000..780bee1c538 --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor40DL_f3dlite_Bauble2_R @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor40DL_f3dlite_Bauble3_B b/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor40DL_f3dlite_Bauble3_B new file mode 100644 index 00000000000..7af3c0fdfa9 --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor40DL_f3dlite_Bauble3_B @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor40DL_f3dlite_Bauble3_G b/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor40DL_f3dlite_Bauble3_G new file mode 100644 index 00000000000..5ec29067d2d --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor40DL_f3dlite_Bauble3_G @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor40DL_f3dlite_Bauble3_R b/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor40DL_f3dlite_Bauble3_R new file mode 100644 index 00000000000..6bfad5f53ee --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor40DL_f3dlite_Bauble3_R @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor40DL_f3dlite_Wrapping_B_32 b/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor40DL_f3dlite_Wrapping_B_32 new file mode 100644 index 00000000000..1425245e052 --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor40DL_f3dlite_Wrapping_B_32 @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor40DL_f3dlite_Wrapping_G_32 b/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor40DL_f3dlite_Wrapping_G_32 new file mode 100644 index 00000000000..5f67d4de108 --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor40DL_f3dlite_Wrapping_G_32 @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor40DL_f3dlite_Wrapping_G_64 b/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor40DL_f3dlite_Wrapping_G_64 new file mode 100644 index 00000000000..c3a973f3929 --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor40DL_f3dlite_Wrapping_G_64 @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor40DL_f3dlite_Wrapping_R_32 b/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor40DL_f3dlite_Wrapping_R_32 new file mode 100644 index 00000000000..f330997c280 --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor40DL_f3dlite_Wrapping_R_32 @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor40DL_f3dlite_Wrapping_R_64 b/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor40DL_f3dlite_Wrapping_R_64 new file mode 100644 index 00000000000..ddba85e48a7 --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor40DL_f3dlite_Wrapping_R_64 @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor50DL_f3dlite_Bauble1_B b/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor50DL_f3dlite_Bauble1_B new file mode 100644 index 00000000000..0562ad51423 --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor50DL_f3dlite_Bauble1_B @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor50DL_f3dlite_Bauble1_G b/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor50DL_f3dlite_Bauble1_G new file mode 100644 index 00000000000..424720964de --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor50DL_f3dlite_Bauble1_G @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor50DL_f3dlite_Bauble2_G b/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor50DL_f3dlite_Bauble2_G new file mode 100644 index 00000000000..a3ebffce584 --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor50DL_f3dlite_Bauble2_G @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor50DL_f3dlite_Bauble2_R b/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor50DL_f3dlite_Bauble2_R new file mode 100644 index 00000000000..780bee1c538 --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor50DL_f3dlite_Bauble2_R @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor50DL_f3dlite_Bauble3_B b/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor50DL_f3dlite_Bauble3_B new file mode 100644 index 00000000000..7af3c0fdfa9 --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor50DL_f3dlite_Bauble3_B @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor50DL_f3dlite_Bauble3_R b/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor50DL_f3dlite_Bauble3_R new file mode 100644 index 00000000000..6bfad5f53ee --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor50DL_f3dlite_Bauble3_R @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor60DL_f3dlite_Bauble1_B b/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor60DL_f3dlite_Bauble1_B new file mode 100644 index 00000000000..0562ad51423 --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor60DL_f3dlite_Bauble1_B @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor60DL_f3dlite_Bauble1_G b/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor60DL_f3dlite_Bauble1_G new file mode 100644 index 00000000000..424720964de --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor60DL_f3dlite_Bauble1_G @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor60DL_f3dlite_Bauble1_R b/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor60DL_f3dlite_Bauble1_R new file mode 100644 index 00000000000..ed8997bac1c --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor60DL_f3dlite_Bauble1_R @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor60DL_f3dlite_Bauble2_G b/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor60DL_f3dlite_Bauble2_G new file mode 100644 index 00000000000..a3ebffce584 --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor60DL_f3dlite_Bauble2_G @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor60DL_f3dlite_Bauble2_R b/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor60DL_f3dlite_Bauble2_R new file mode 100644 index 00000000000..780bee1c538 --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor60DL_f3dlite_Bauble2_R @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor60DL_f3dlite_Bauble3_G b/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor60DL_f3dlite_Bauble3_G new file mode 100644 index 00000000000..5ec29067d2d --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor60DL_f3dlite_Bauble3_G @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor60DL_f3dlite_Wrapping_B_64 b/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor60DL_f3dlite_Wrapping_B_64 new file mode 100644 index 00000000000..e2cb1c6af43 --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor60DL_f3dlite_Wrapping_B_64 @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor60DL_f3dlite_Wrapping_G_32 b/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor60DL_f3dlite_Wrapping_G_32 new file mode 100644 index 00000000000..5f67d4de108 --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor60DL_f3dlite_Wrapping_G_32 @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor60DL_f3dlite_Wrapping_R_32 b/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor60DL_f3dlite_Wrapping_R_32 new file mode 100644 index 00000000000..f330997c280 --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor60DL_f3dlite_Wrapping_R_32 @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor60DL_f3dlite_Wrapping_R_64 b/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor60DL_f3dlite_Wrapping_R_64 new file mode 100644 index 00000000000..ddba85e48a7 --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor60DL_f3dlite_Wrapping_R_64 @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor70DL_f3dlite_Bauble1_G b/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor70DL_f3dlite_Bauble1_G new file mode 100644 index 00000000000..424720964de --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor70DL_f3dlite_Bauble1_G @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor70DL_f3dlite_Bauble1_R b/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor70DL_f3dlite_Bauble1_R new file mode 100644 index 00000000000..ed8997bac1c --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor70DL_f3dlite_Bauble1_R @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor70DL_f3dlite_Bauble2_G b/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor70DL_f3dlite_Bauble2_G new file mode 100644 index 00000000000..a3ebffce584 --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor70DL_f3dlite_Bauble2_G @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor70DL_f3dlite_Bauble2_R b/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor70DL_f3dlite_Bauble2_R new file mode 100644 index 00000000000..780bee1c538 --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor70DL_f3dlite_Bauble2_R @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor70DL_f3dlite_Bauble3_B b/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor70DL_f3dlite_Bauble3_B new file mode 100644 index 00000000000..7af3c0fdfa9 --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor70DL_f3dlite_Bauble3_B @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor70DL_f3dlite_Bauble3_G b/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor70DL_f3dlite_Bauble3_G new file mode 100644 index 00000000000..5ec29067d2d --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor70DL_f3dlite_Bauble3_G @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor70DL_f3dlite_Bauble3_R b/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor70DL_f3dlite_Bauble3_R new file mode 100644 index 00000000000..6bfad5f53ee --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor70DL_f3dlite_Bauble3_R @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor80DL_f3dlite_Bauble1_B b/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor80DL_f3dlite_Bauble1_B new file mode 100644 index 00000000000..0562ad51423 --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor80DL_f3dlite_Bauble1_B @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor80DL_f3dlite_Bauble1_R b/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor80DL_f3dlite_Bauble1_R new file mode 100644 index 00000000000..ed8997bac1c --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor80DL_f3dlite_Bauble1_R @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor80DL_f3dlite_Bauble2_G b/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor80DL_f3dlite_Bauble2_G new file mode 100644 index 00000000000..a3ebffce584 --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor80DL_f3dlite_Bauble2_G @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor80DL_f3dlite_Bauble3_B b/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor80DL_f3dlite_Bauble3_B new file mode 100644 index 00000000000..7af3c0fdfa9 --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor80DL_f3dlite_Bauble3_B @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor80DL_f3dlite_Bauble3_G b/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor80DL_f3dlite_Bauble3_G new file mode 100644 index 00000000000..5ec29067d2d --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor80DL_f3dlite_Bauble3_G @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor80DL_f3dlite_Bauble3_R b/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor80DL_f3dlite_Bauble3_R new file mode 100644 index 00000000000..6bfad5f53ee --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor80DL_f3dlite_Bauble3_R @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor80DL_f3dlite_Wrapping_B_32 b/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor80DL_f3dlite_Wrapping_B_32 new file mode 100644 index 00000000000..1425245e052 --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor80DL_f3dlite_Wrapping_B_32 @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor80DL_f3dlite_Wrapping_G_32 b/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor80DL_f3dlite_Wrapping_G_32 new file mode 100644 index 00000000000..5f67d4de108 --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor80DL_f3dlite_Wrapping_G_32 @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor80DL_f3dlite_Wrapping_R_32 b/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor80DL_f3dlite_Wrapping_R_32 new file mode 100644 index 00000000000..f330997c280 --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor80DL_f3dlite_Wrapping_R_32 @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor90DL_f3dlite_Bauble1_B b/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor90DL_f3dlite_Bauble1_B new file mode 100644 index 00000000000..0562ad51423 --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor90DL_f3dlite_Bauble1_B @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor90DL_f3dlite_Bauble1_G b/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor90DL_f3dlite_Bauble1_G new file mode 100644 index 00000000000..424720964de --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor90DL_f3dlite_Bauble1_G @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor90DL_f3dlite_Bauble1_R b/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor90DL_f3dlite_Bauble1_R new file mode 100644 index 00000000000..ed8997bac1c --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor90DL_f3dlite_Bauble1_R @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor90DL_f3dlite_Bauble2_B b/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor90DL_f3dlite_Bauble2_B new file mode 100644 index 00000000000..a2fb866622d --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor90DL_f3dlite_Bauble2_B @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor90DL_f3dlite_Bauble2_R b/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor90DL_f3dlite_Bauble2_R new file mode 100644 index 00000000000..780bee1c538 --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor90DL_f3dlite_Bauble2_R @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor90DL_f3dlite_Bauble3_B b/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor90DL_f3dlite_Bauble3_B new file mode 100644 index 00000000000..7af3c0fdfa9 --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor90DL_f3dlite_Bauble3_B @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor90DL_f3dlite_Bauble3_G b/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor90DL_f3dlite_Bauble3_G new file mode 100644 index 00000000000..5ec29067d2d --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor90DL_f3dlite_Bauble3_G @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor90DL_f3dlite_Bauble3_R b/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor90DL_f3dlite_Bauble3_R new file mode 100644 index 00000000000..6bfad5f53ee --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/mat_gXmasDecor90DL_f3dlite_Bauble3_R @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/mat_gXmasStarDL_f3dlite_Glow b/soh/assets/custom/objects/object_xmas_tree/mat_gXmasStarDL_f3dlite_Glow new file mode 100644 index 00000000000..381668c5c49 --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/mat_gXmasStarDL_f3dlite_Glow @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/mat_gXmasStarDL_f3dlite_OrnamentGold b/soh/assets/custom/objects/object_xmas_tree/mat_gXmasStarDL_f3dlite_OrnamentGold new file mode 100644 index 00000000000..b657da8a753 --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/mat_gXmasStarDL_f3dlite_OrnamentGold @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/mat_gXmasTreeDL_f3dlite_TreeBrown b/soh/assets/custom/objects/object_xmas_tree/mat_gXmasTreeDL_f3dlite_TreeBrown new file mode 100644 index 00000000000..37fdb10d29f --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/mat_gXmasTreeDL_f3dlite_TreeBrown @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/mat_gXmasTreeDL_f3dlite_TreeGreen b/soh/assets/custom/objects/object_xmas_tree/mat_gXmasTreeDL_f3dlite_TreeGreen new file mode 100644 index 00000000000..119341c7017 --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/mat_gXmasTreeDL_f3dlite_TreeGreen @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/objects/object_xmas_tree/mat_gXmasTreeDL_f3dlite_TreeTip b/soh/assets/custom/objects/object_xmas_tree/mat_gXmasTreeDL_f3dlite_TreeTip new file mode 100644 index 00000000000..9baaadbf554 --- /dev/null +++ b/soh/assets/custom/objects/object_xmas_tree/mat_gXmasTreeDL_f3dlite_TreeTip @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/soh/assets/custom/presets/Main Vanilla+.json b/soh/assets/custom/presets/Enhancements - Curated First Time Vanilla.json similarity index 96% rename from soh/assets/custom/presets/Main Vanilla+.json rename to soh/assets/custom/presets/Enhancements - Curated First Time Vanilla.json index 1ac97a5671e..3b483d5ce0a 100644 --- a/soh/assets/custom/presets/Main Vanilla+.json +++ b/soh/assets/custom/presets/Enhancements - Curated First Time Vanilla.json @@ -52,6 +52,6 @@ "gRandoEnhancements": null } }, - "presetName": "Main Vanilla+", + "presetName": "Enhancements - Curated First Time Vanilla", "isBuiltIn": true } \ No newline at end of file diff --git a/soh/assets/custom/presets/Main Randomizer.json b/soh/assets/custom/presets/Enhancements - Curated Randomizer.json similarity index 98% rename from soh/assets/custom/presets/Main Randomizer.json rename to soh/assets/custom/presets/Enhancements - Curated Randomizer.json index fae2fe95777..78d1e5d7507 100644 --- a/soh/assets/custom/presets/Main Randomizer.json +++ b/soh/assets/custom/presets/Enhancements - Curated Randomizer.json @@ -133,6 +133,6 @@ "gRandoEnhancements": null } }, - "presetName": "Main Randomizer", + "presetName": "Enhancements - Curated Randomizer", "isBuiltIn": true } \ No newline at end of file diff --git a/soh/assets/custom/presets/Main Enhanced.json b/soh/assets/custom/presets/Enhancements - Curated Returning Vanilla.json similarity index 97% rename from soh/assets/custom/presets/Main Enhanced.json rename to soh/assets/custom/presets/Enhancements - Curated Returning Vanilla.json index eabda809fa5..8a00f87f353 100644 --- a/soh/assets/custom/presets/Main Enhanced.json +++ b/soh/assets/custom/presets/Enhancements - Curated Returning Vanilla.json @@ -55,6 +55,6 @@ "gRandoEnhancements": null } }, - "presetName": "Main Enhanced", + "presetName": "Enhancements - Curated Returning Vanilla", "isBuiltIn": true } \ No newline at end of file diff --git a/soh/assets/custom/presets/Main Default.json b/soh/assets/custom/presets/Enhancements - Reset to Default.json similarity index 77% rename from soh/assets/custom/presets/Main Default.json rename to soh/assets/custom/presets/Enhancements - Reset to Default.json index 7d87c3d9567..c41f729610b 100644 --- a/soh/assets/custom/presets/Main Default.json +++ b/soh/assets/custom/presets/Enhancements - Reset to Default.json @@ -6,6 +6,6 @@ "gRandoEnhancements": null } }, - "presetName": "Main Default", + "presetName": "Enhancements - Reset to Default", "isBuiltIn": true } \ No newline at end of file diff --git a/soh/assets/custom/presets/Rando Advanced.json b/soh/assets/custom/presets/Rando Seed Settings - Advanced.json similarity index 97% rename from soh/assets/custom/presets/Rando Advanced.json rename to soh/assets/custom/presets/Rando Seed Settings - Advanced.json index 4042c0688cb..f8fb2b9d167 100644 --- a/soh/assets/custom/presets/Rando Advanced.json +++ b/soh/assets/custom/presets/Rando Seed Settings - Advanced.json @@ -64,6 +64,6 @@ } } }, - "presetName": "Rando Advanced", + "presetName": "Rando Seed Settings - Advanced", "isBuiltIn": true } \ No newline at end of file diff --git a/soh/assets/custom/presets/Rando Beginner.json b/soh/assets/custom/presets/Rando Seed Settings - Beginner.json similarity index 96% rename from soh/assets/custom/presets/Rando Beginner.json rename to soh/assets/custom/presets/Rando Seed Settings - Beginner.json index 595734fe043..be4c185675c 100644 --- a/soh/assets/custom/presets/Rando Beginner.json +++ b/soh/assets/custom/presets/Rando Seed Settings - Beginner.json @@ -46,6 +46,6 @@ } } }, - "presetName": "Rando Beginner", + "presetName": "Rando Seed Settings - Beginner", "isBuiltIn": true } \ No newline at end of file diff --git a/soh/assets/custom/presets/Rando Hell Mode.json b/soh/assets/custom/presets/Rando Seed Settings - Hell Mode.json similarity index 98% rename from soh/assets/custom/presets/Rando Hell Mode.json rename to soh/assets/custom/presets/Rando Seed Settings - Hell Mode.json index cdcceb45107..5e0d7e5863f 100644 --- a/soh/assets/custom/presets/Rando Hell Mode.json +++ b/soh/assets/custom/presets/Rando Seed Settings - Hell Mode.json @@ -78,6 +78,6 @@ } } }, - "presetName": "Rando Hell Mode", + "presetName": "Rando Seed Settings - Hell Mode", "isBuiltIn": true } \ No newline at end of file diff --git a/soh/assets/custom/presets/Rando Default.json b/soh/assets/custom/presets/Rando Seed Settings - Reset to Default.json similarity index 64% rename from soh/assets/custom/presets/Rando Default.json rename to soh/assets/custom/presets/Rando Seed Settings - Reset to Default.json index f5c5818e94d..b8732382118 100644 --- a/soh/assets/custom/presets/Rando Default.json +++ b/soh/assets/custom/presets/Rando Seed Settings - Reset to Default.json @@ -4,6 +4,6 @@ "gRandoSettings": null } }, - "presetName": "Rando Default", + "presetName": "Rando Seed Settings - Reset to Default", "isBuiltIn": true } \ No newline at end of file diff --git a/soh/assets/custom/presets/Rando Standard.json b/soh/assets/custom/presets/Rando Seed Settings - Standard.json similarity index 97% rename from soh/assets/custom/presets/Rando Standard.json rename to soh/assets/custom/presets/Rando Seed Settings - Standard.json index ad91912de83..b89a36abc25 100644 --- a/soh/assets/custom/presets/Rando Standard.json +++ b/soh/assets/custom/presets/Rando Seed Settings - Standard.json @@ -58,6 +58,6 @@ } } }, - "presetName": "Rando Standard", + "presetName": "Rando Seed Settings - Standard", "isBuiltIn": true } \ No newline at end of file diff --git a/soh/assets/custom/textures/parameter_static/gArchipelagoJunk.png b/soh/assets/custom/textures/parameter_static/gArchipelagoJunk.png new file mode 100644 index 00000000000..8103d86aa18 Binary files /dev/null and b/soh/assets/custom/textures/parameter_static/gArchipelagoJunk.png differ diff --git a/soh/assets/custom/textures/parameter_static/gArchipelagoOutline.rgba32.png b/soh/assets/custom/textures/parameter_static/gArchipelagoOutline.rgba32.png new file mode 100644 index 00000000000..3c917fbd60b Binary files /dev/null and b/soh/assets/custom/textures/parameter_static/gArchipelagoOutline.rgba32.png differ diff --git a/soh/assets/custom/textures/parameter_static/gArchipelagoProgressive.png b/soh/assets/custom/textures/parameter_static/gArchipelagoProgressive.png new file mode 100644 index 00000000000..953a8170580 Binary files /dev/null and b/soh/assets/custom/textures/parameter_static/gArchipelagoProgressive.png differ diff --git a/soh/assets/custom/textures/parameter_static/gArchipelagoUseful.png b/soh/assets/custom/textures/parameter_static/gArchipelagoUseful.png new file mode 100644 index 00000000000..05d2e0c2dcb Binary files /dev/null and b/soh/assets/custom/textures/parameter_static/gArchipelagoUseful.png differ diff --git a/soh/assets/custom/textures/parameter_static/gHolidayPiece.rgba32.png b/soh/assets/custom/textures/parameter_static/gHolidayPiece.rgba32.png new file mode 100644 index 00000000000..eace659af29 Binary files /dev/null and b/soh/assets/custom/textures/parameter_static/gHolidayPiece.rgba32.png differ diff --git a/soh/assets/custom/textures/title_static/gFileSelArchiButtonTex.ia16.png b/soh/assets/custom/textures/title_static/gFileSelArchiButtonTex.ia16.png new file mode 100644 index 00000000000..e6b80d6c54a Binary files /dev/null and b/soh/assets/custom/textures/title_static/gFileSelArchiButtonTex.ia16.png differ diff --git a/soh/assets/soh_assets.h b/soh/assets/soh_assets.h index 3c3ddcf5a7e..a1adf20607b 100644 --- a/soh/assets/soh_assets.h +++ b/soh/assets/soh_assets.h @@ -8,6 +8,27 @@ // On Mac, not using aligned resource names was causing crashes in release builds // objects +#define dgLinkAdultHatTrimDL "__OTR__objects/object_festivehats/gLinkAdultHatTrimDL" +static const ALIGN_ASSET(2) char gLinkAdultHatTrimDL[] = dgLinkAdultHatTrimDL; + +#define dgLinkAdultPompomDL "__OTR__objects/object_festivehats/gLinkAdultPompomDL" +static const ALIGN_ASSET(2) char gLinkAdultPompomDL[] = dgLinkAdultPompomDL; + +#define dgLinkChildHatTrimDL "__OTR__objects/object_festivehats/gLinkChildHatTrimDL" +static const ALIGN_ASSET(2) char gLinkChildHatTrimDL[] = dgLinkChildHatTrimDL; + +#define dgPaperCrownGenericDL "__OTR__objects/object_festivehats/gPaperCrownGenericDL" +static const ALIGN_ASSET(2) char gPaperCrownGenericDL[] = dgPaperCrownGenericDL; + +#define dgSantaHatGenericDL "__OTR__objects/object_festivehats/gSantaHatGenericDL" +static const ALIGN_ASSET(2) char gSantaHatGenericDL[] = dgSantaHatGenericDL; + +#define dgHorseAntlersDL "__OTR__objects/object_festivehats/gHorseAntlersDL" +static const ALIGN_ASSET(2) char gHorseAntlersDL[] = dgHorseAntlersDL; + +#define dgEponaRudolphHatDL "__OTR__objects/object_festivehats/gEponaRudolphHatDL" +static const ALIGN_ASSET(2) char gEponaRudolphHatDL[] = dgEponaRudolphHatDL; + #define dgChristmasGreenTreasureChestFrontTex "__OTR__objects/object_box/gChristmasGreenTreasureChestFrontTex" static const ALIGN_ASSET(2) char gChristmasGreenTreasureChestFrontTex[] = dgChristmasGreenTreasureChestFrontTex; @@ -38,12 +59,21 @@ static const ALIGN_ASSET(2) char gSkullTreasureChestFrontTex[] = dgSkullTreasure #define dgSkullTreasureChestSideAndTopTex "__OTR__objects/object_box/gSkullTreasureChestSideAndTopTex" static const ALIGN_ASSET(2) char gSkullTreasureChestSideAndTopTex[] = dgSkullTreasureChestSideAndTopTex; +#define dgSnowballDL "__OTR__objects/custom_snowball/snowball" +static const ALIGN_ASSET(2) char gSnowballDL[] = dgSnowballDL; + #define dgTitleRandomizerSubtitleTex "__OTR__objects/object_mag/gTitleRandomizerSubtitleTex" static const ALIGN_ASSET(2) char gTitleRandomizerSubtitleTex[] = dgTitleRandomizerSubtitleTex; #define dgTitleBossRushSubtitleTex "__OTR__objects/object_mag/gTitleBossRushSubtitleTex" static const ALIGN_ASSET(2) char gTitleBossRushSubtitleTex[] = dgTitleBossRushSubtitleTex; +#define dgTitleArchipelagoSubtilteTex "__OTR__objects/object_mag/gTitleArchipelagoSubtitleTex" +static const ALIGN_ASSET(2) char gTitleArchipelagoSubtitleTex[] = dgTitleArchipelagoSubtilteTex; + +#define dgTitleRogueLikeSubtitleTex "__OTR__objects/object_mag/gTitleRogueLikeSubtitleTex" +static const ALIGN_ASSET(2) char gTitleRogueLikeSubtitleTex[] = dgTitleRogueLikeSubtitleTex; + #define dgOcarinaAButtonDL "__OTR__objects/object_ocarina_a_button/gOcarinaAButtonDL" static const ALIGN_ASSET(2) char gOcarinaAButtonDL[] = dgOcarinaAButtonDL; @@ -71,6 +101,18 @@ static const ALIGN_ASSET(2) char gTriforcePiece2DL[] = dgTriforcePiece2DL; #define dgTriforcePieceCompletedDL "__OTR__objects/object_triforce_completed/gTriforcePieceCompletedDL" static const ALIGN_ASSET(2) char gTriforcePieceCompletedDL[] = dgTriforcePieceCompletedDL; +#define dgHolidayPiece0DL "__OTR__objects/object_holiday_piece_0/gHolidayPiece0DL" +static const ALIGN_ASSET(2) char gHolidayPiece0DL[] = dgHolidayPiece0DL; + +#define dgHolidayPiece1DL "__OTR__objects/object_holiday_piece_1/gHolidayPiece1DL" +static const ALIGN_ASSET(2) char gHolidayPiece1DL[] = dgHolidayPiece1DL; + +#define dgHolidayPiece2DL "__OTR__objects/object_holiday_piece_2/gHolidayPiece2DL" +static const ALIGN_ASSET(2) char gHolidayPiece2DL[] = dgHolidayPiece2DL; + +#define dgHolidayPieceCompletedDL "__OTR__objects/object_holiday_completed/gHolidayPieceCompletedDL" +static const ALIGN_ASSET(2) char gHolidayPieceCompletedDL[] = dgHolidayPieceCompletedDL; + #define dgBossSoulSkullDL "__OTR__objects/object_boss_soul/gGIBossSoulSkullDL" static const ALIGN_ASSET(2) char gBossSoulSkullDL[] = dgBossSoulSkullDL; @@ -143,6 +185,57 @@ static const ALIGN_ASSET(2) char gFishingPoleGiDL[] = dgFishingPoleGiDL; #define dgMysteryItemDL "__OTR__objects/object_mystery_item/gMysteryItemDL" static const ALIGN_ASSET(2) char gMysteryItemDL[] = dgMysteryItemDL; +#define dgXmasTreeDL "__OTR__objects/object_xmas_tree/gXmasTreeDL" +static const ALIGN_ASSET(2) char gXmasTreeDL[] = dgXmasTreeDL; + +#define dgXmasDecor10DL "__OTR__objects/object_xmas_tree/gXmasDecor10DL" +static const ALIGN_ASSET(2) char gXmasDecor10DL[] = dgXmasDecor10DL; + +#define dgXmasDecor20DL "__OTR__objects/object_xmas_tree/gXmasDecor20DL" +static const ALIGN_ASSET(2) char gXmasDecor20DL[] = dgXmasDecor20DL; + +#define dgXmasDecor30DL "__OTR__objects/object_xmas_tree/gXmasDecor30DL" +static const ALIGN_ASSET(2) char gXmasDecor30DL[] = dgXmasDecor30DL; + +#define dgXmasDecor40DL "__OTR__objects/object_xmas_tree/gXmasDecor40DL" +static const ALIGN_ASSET(2) char gXmasDecor40DL[] = dgXmasDecor40DL; + +#define dgXmasDecor50DL "__OTR__objects/object_xmas_tree/gXmasDecor50DL" +static const ALIGN_ASSET(2) char gXmasDecor50DL[] = dgXmasDecor50DL; + +#define dgXmasDecor60DL "__OTR__objects/object_xmas_tree/gXmasDecor60DL" +static const ALIGN_ASSET(2) char gXmasDecor60DL[] = dgXmasDecor60DL; + +#define dgXmasDecor70DL "__OTR__objects/object_xmas_tree/gXmasDecor70DL" +static const ALIGN_ASSET(2) char gXmasDecor70DL[] = dgXmasDecor70DL; + +#define dgXmasDecor80DL "__OTR__objects/object_xmas_tree/gXmasDecor80DL" +static const ALIGN_ASSET(2) char gXmasDecor80DL[] = dgXmasDecor80DL; + +#define dgXmasDecor90DL "__OTR__objects/object_xmas_tree/gXmasDecor90DL" +static const ALIGN_ASSET(2) char gXmasDecor90DL[] = dgXmasDecor90DL; + +#define dgXmasDecor100DL "__OTR__objects/object_xmas_tree/gXmasDecor100DL" +static const ALIGN_ASSET(2) char gXmasDecor100DL[] = dgXmasDecor100DL; + +#define dgXmasStarDL "__OTR__objects/object_xmas_tree/gXmasStarDL" +static const ALIGN_ASSET(2) char gXmasStarDL[] = dgXmasStarDL; + +#define dgPenguinDL "__OTR__objects/object_penguin/object_penguin_DL" +static const ALIGN_ASSET(2) char gPenguinDL[] = dgPenguinDL; + +#define dgKakarikoDecorDL "__OTR__objects/object_kakariko_decor/gKakarikoDecorDL" +static const ALIGN_ASSET(2) char gKakarikoDecorDL[] = dgKakarikoDecorDL; + +#define dgTempleOfTimeDecorDL "__OTR__objects/object_temple_of_time_decor/gTempleOfTimeDecorDL" +static const ALIGN_ASSET(2) char gTempleOfTimeDecorDL[] = dgTempleOfTimeDecorDL; + +#define dgKakarikoChildDecorDL "__OTR__objects/object_kakariko_decor/gKakarikoChildDecorDL" +static const ALIGN_ASSET(2) char gKakarikoChildDecorDL[] = dgKakarikoChildDecorDL; + +#define dgKakarikoAdultDecorDL "__OTR__objects/object_kakariko_decor/gKakarikoAdultDecorDL" +static const ALIGN_ASSET(2) char gKakarikoAdultDecorDL[] = dgKakarikoAdultDecorDL; + #define dgBombchuBagBodyDL "__OTR__objects/object_bombchubag/gBombchuBagBodyDL" static const ALIGN_ASSET(2) char gBombchuBagBodyDL[] = dgBombchuBagBodyDL; @@ -368,6 +461,9 @@ static const ALIGN_ASSET(2) char gTriforcePieceTex[] = dgTriforcePiece; #define dgWTriforcePiece "__OTR__textures/parameter_static/gWTriforcePiece" static const ALIGN_ASSET(2) char gWTriforcePieceTex[] = dgWTriforcePiece; +#define dgHolidayPiece "__OTR__textures/parameter_static/gHolidayPiece" +static const ALIGN_ASSET(2) char gHolidayPieceTex[] = dgHolidayPiece; + #define dgSplitEntrance "__OTR__textures/parameter_static/gSplitEntrance" static const ALIGN_ASSET(2) char gSplitEntranceTex[] = dgSplitEntrance; @@ -413,6 +509,9 @@ static const ALIGN_ASSET(2) char gFileSelBossRushSettingsJPNText[] = dgFileSelBo #define dgFileSelRANDButtonTex "__OTR__textures/title_static/gFileSelRANDButtonTex" static const ALIGN_ASSET(2) char gFileSelRANDButtonTex[] = dgFileSelRANDButtonTex; +#define dgFileSelArchiButtonTex "__OTR__textures/title_static/gFileSelArchiButtonTex" +static const ALIGN_ASSET(2) char gFileSelArchiButtonTex[] = dgFileSelArchiButtonTex; + #define dgFileSelLangEnglishENGTex "__OTR__textures/title_static/gFileSelLangEnglishENGTex" static const ALIGN_ASSET(2) char gFileSelLangEnglishENGTex[] = dgFileSelLangEnglishENGTex; @@ -459,3 +558,17 @@ static const ALIGN_ASSET(2) char gShipLogoDL[] = dgShipLogoDL; #define dnintendo_rogo_static_Tex_LUS_000000 "__OTR__textures/nintendo_rogo_static/nintendo_rogo_static_Tex_LUS_000000" static const ALIGN_ASSET(2) char nintendo_rogo_static_Tex_LUS_000000[] = dnintendo_rogo_static_Tex_LUS_000000; + +// Archipelago Item Icons +#define dgArchipelagoItemTex "__OTR__textures/parameter_static/gArchipelagoOutline" +static const ALIGN_ASSET(2) char gArchipelagoItemTex[] = dgArchipelagoItemTex; + +// Archipelago Item Models +#define dgArchipelagoItemDL "__OTR__objects/object_archipelago_item/gArchipelagoItemDL" +static const ALIGN_ASSET(2) char gArchipelagoItemDL[] = dgArchipelagoItemDL; + +#define dgArchipelagoProgressiveDL "__OTR__objects/object_archipelago_item/gArchipelagoProgressiveDL" +static const ALIGN_ASSET(2) char gArchipelagoProgressiveDL[] = dgArchipelagoProgressiveDL; + +#define dgArchipelagoJunkDL "__OTR__objects/object_archipelago_item/gArchipelagoJunkDL" +static const ALIGN_ASSET(2) char gArchipelagoJunkDL[] = dgArchipelagoJunkDL; diff --git a/soh/assets/sources/archipelago/ap_logo.blend b/soh/assets/sources/archipelago/ap_logo.blend new file mode 100644 index 00000000000..97de9159347 Binary files /dev/null and b/soh/assets/sources/archipelago/ap_logo.blend differ diff --git a/soh/assets/sources/archipelago/ap_logo_arrow.blend b/soh/assets/sources/archipelago/ap_logo_arrow.blend new file mode 100644 index 00000000000..9dc5de29293 Binary files /dev/null and b/soh/assets/sources/archipelago/ap_logo_arrow.blend differ diff --git a/soh/assets/sources/archipelago/ap_logo_junk.blend b/soh/assets/sources/archipelago/ap_logo_junk.blend new file mode 100644 index 00000000000..956a60cf3e3 Binary files /dev/null and b/soh/assets/sources/archipelago/ap_logo_junk.blend differ diff --git a/soh/include/functions.h b/soh/include/functions.h index 11dbf8c811a..bbadddca101 100644 --- a/soh/include/functions.h +++ b/soh/include/functions.h @@ -1069,6 +1069,7 @@ void Interface_LoadItemIcon1(PlayState* play, u16 button); void Interface_LoadItemIcon2(PlayState* play, u16 button); void func_80084BF4(PlayState* play, u16 flag); uint16_t Interface_DrawTextLine(GraphicsContext* gfx, char text[], int16_t x, int16_t y, uint16_t colorR, uint16_t colorG, uint16_t colorB, uint16_t colorA, float textScale, uint8_t textShadow); +uint16_t Interface_DrawTextLineOverlay(GraphicsContext* gfx, char text[], int16_t x, int16_t y, uint16_t colorR, uint16_t colorG, uint16_t colorB, uint16_t colorA, float textScale, uint8_t textShadow); u8 Item_Give(PlayState* play, u8 item); u16 Randomizer_Item_Give(PlayState* play, GetItemEntry giEntry); u8 Item_CheckObtainability(u8 item); @@ -2457,6 +2458,9 @@ void Interface_RandoRestoreSwordless(void); s32 Ship_CalcShouldDrawAndUpdate(PlayState* play, Actor* actor, Vec3f* projectedPos, f32 projectedW, bool* shouldDraw, bool* shouldUpdate); +// Exposing for Roc's Feather +void func_80838940(Player* this, LinkAnimationHeader* anim, f32 arg2, PlayState* play, u16 sfxId); + // #endregion #ifdef __cplusplus diff --git a/soh/include/z64.h b/soh/include/z64.h index 70b877f30ae..acb693ac5bb 100644 --- a/soh/include/z64.h +++ b/soh/include/z64.h @@ -1593,7 +1593,7 @@ typedef struct FileChooseContext { /* 0x1CAD4 */ s16 newFileNameCharCount; /* 0x1CAD6 */ s16 unk_1CAD6[5]; s16 logoAlpha; - s8 questType[3]; // 0 for Normal, 1 for MQ + s8 questType[4]; // 0 for Normal, 1 for MQ StickDirectionPrompt stickLeftPrompt; StickDirectionPrompt stickRightPrompt; f32 arrowAnimTween; @@ -1607,6 +1607,10 @@ typedef struct FileChooseContext { uint8_t randomizerIndex; int16_t randomizerUIAlpha; uint16_t randomizerArrowOffset; + uint8_t archipelagoIndex; + int16_t archipelagoUIAlpha; + uint16_t archipelagoArrowOffset; + } FileChooseContext; // size = 0x1CAE0 // Macros for `EntranceInfo.field` diff --git a/soh/include/z64actor.h b/soh/include/z64actor.h index 77172bda4aa..07b50eac1d4 100644 --- a/soh/include/z64actor.h +++ b/soh/include/z64actor.h @@ -266,6 +266,10 @@ typedef struct Actor { /* 0x134 */ ActorFunc draw; // Draw Routine. Called by `Actor_Draw` /* 0x138 */ ActorResetFunc reset; /* 0x13C */ char dbgPad[0x10]; // Padding that only exists in the debug rom + // #region SOH [General] TODO convert to OE + /* */ u8 maximumHealth; // Max health value for use with health bars, set on actor init + /* */ u8 isShiny; + // #endregion } Actor; // size = 0x14C typedef enum { diff --git a/soh/include/z64item.h b/soh/include/z64item.h index b1fa897fe6e..866c4e20d43 100644 --- a/soh/include/z64item.h +++ b/soh/include/z64item.h @@ -309,6 +309,7 @@ typedef enum { /* 0x99 */ ITEM_STICK_UPGRADE_30, /* 0x9A */ ITEM_NUT_UPGRADE_30, /* 0x9B */ ITEM_NUT_UPGRADE_40, + /* 0x9C */ ITEM_SHIP, // SOH [Enhancement] Added to enable custom item gives /* 0xFC */ ITEM_LAST_USED = 0xFC, /* 0xFE */ ITEM_NONE_FE = 0xFE, /* 0xFF */ ITEM_NONE = 0xFF @@ -460,7 +461,8 @@ typedef enum { /* 0x7B */ GI_BULLET_BAG_50, /* 0x7C */ GI_ICE_TRAP, // freezes link when opened from a chest /* 0x7D */ GI_TEXT_0, // no model appears over Link, shows text id 0 (pocket egg) - /* 0x84 */ GI_MAX + /* 0x7E */ GI_SHIP, // SOH [Enhancement] Added to enable custom item gives + /* 0x7E */ GI_MAX } GetItemID; typedef enum { diff --git a/soh/include/z64save.h b/soh/include/z64save.h index 0abfe32d828..d312509110d 100644 --- a/soh/include/z64save.h +++ b/soh/include/z64save.h @@ -8,6 +8,7 @@ #include "soh/Enhancements/gameplaystats.h" #include "soh/Enhancements/randomizer/randomizer_entrance.h" #include "soh/Enhancements/boss-rush/BossRush.h" +#include "soh/Enhancements/RogueLike/Types.h" #define FULL_HEART_HEALTH 0x10 #define STARTING_HEALTH (3 * FULL_HEART_HEALTH) @@ -72,6 +73,8 @@ typedef enum { // Pre-existing IDs for save sections in base code SECTION_ID_ENTRANCES, SECTION_ID_SCENES, SECTION_ID_TRACKER_DATA, + SECTION_ID_ARCHIPELAGO, + SECTION_ID_ROGUELIKE, SECTION_ID_MAX } SaveFuncIDs; @@ -167,14 +170,39 @@ typedef struct ShipRandomizerSaveContextData { u8 bombchuUpgradeLevel; } ShipRandomizerSaveContextData; +typedef struct ShipRogueLikeSaveContextData { + u32 stats[RL_MAX]; + u32 xp; + u32 difficulty; + uint64_t lastActivity; + RogueLikeQuestObject quests[RL_QUEST_ID_MAX]; +} ShipRogueLikeSaveContextData; + typedef struct ShipBossRushSaveContextData { u32 isPaused; u8 options[BR_OPTIONS_MAX]; } ShipBossRushSaveContextData; -typedef union ShipQuestSpecificSaveContextData { +typedef struct ArchipelagoLocationData { + char itemName[100]; + char playerName[17]; +} ArchipelagoLocationData; + +typedef struct ShipArchipelagoSaveContextData { + u8 isArchipelago; + u32 lastReceivedItemIndex; + char roomHash[100]; + char slotName[17]; + char archiUri[50]; + char roomPass[50]; + ArchipelagoLocationData locations[RC_MAX]; +} ShipArchipelagoSaveContextData; + +typedef struct ShipQuestSpecificSaveContextData { ShipRandomizerSaveContextData randomizer; ShipBossRushSaveContextData bossRush; + ShipArchipelagoSaveContextData archipelago; + ShipRogueLikeSaveContextData rogueLike; } ShipQuestSpecificSaveContextData; typedef struct ShipQuestSaveContextData { @@ -318,12 +346,16 @@ typedef enum { /* 01 */ QUEST_MASTER, /* 02 */ QUEST_RANDOMIZER, /* 03 */ QUEST_BOSSRUSH, + /* 04 */ QUEST_ARCHIPELAGO, + /* 04 */ QUEST_ROGUELIKE, } Quest; #define IS_VANILLA (gSaveContext.ship.quest.id == QUEST_NORMAL) #define IS_MASTER_QUEST (gSaveContext.ship.quest.id == QUEST_MASTER) #define IS_RANDO (gSaveContext.ship.quest.id == QUEST_RANDOMIZER) #define IS_BOSS_RUSH (gSaveContext.ship.quest.id == QUEST_BOSSRUSH) +#define IS_ARCHIPELAGO (gSaveContext.ship.quest.data.archipelago.isArchipelago == 1) +#define IS_ROGUELIKE (gSaveContext.ship.quest.id == QUEST_ROGUELIKE) typedef enum { /* 0x00 */ BTN_ENABLED, diff --git a/soh/soh/ActorDB.cpp b/soh/soh/ActorDB.cpp index 70ea29675e6..3c0df4db3e7 100644 --- a/soh/soh/ActorDB.cpp +++ b/soh/soh/ActorDB.cpp @@ -612,8 +612,61 @@ static ActorDBInit EnPartnerInit = { }; extern "C" s16 gEnPartnerId; +#include "src/overlays/actors/ovl_En_Snowball/z_en_snowball.h" +static ActorDBInit EnSnowballInit = { + "En_Snowball", + "Snowball", + ACTORCAT_ITEMACTION, + (ACTOR_FLAG_UPDATE_CULLING_DISABLED | ACTOR_FLAG_DRAW_CULLING_DISABLED | ACTOR_FLAG_HOOKSHOT_PULLS_ACTOR | + ACTOR_FLAG_CAN_PRESS_SWITCHES), + OBJECT_GAMEPLAY_KEEP, + sizeof(EnSnowball), + (ActorFunc)EnSnowball_Init, + (ActorFunc)EnSnowball_Destroy, + (ActorFunc)EnSnowball_Update, + (ActorFunc)EnSnowball_Draw, + nullptr, +}; +extern "C" s16 gEnSnowballId; + +#include "src/overlays/actors/ovl_En_ChristmasTree/z_en_christmastree.h" +static ActorDBInit EnChristmasTreeInit = { + "En_ChristmasTree", + "Christmas Tree", + ACTORCAT_PROP, + (ACTOR_FLAG_ATTENTION_ENABLED | ACTOR_FLAG_FRIENDLY | ACTOR_FLAG_UPDATE_CULLING_DISABLED | + ACTOR_FLAG_DRAW_CULLING_DISABLED), + OBJECT_GAMEPLAY_KEEP, + sizeof(EnChristmasTree), + (ActorFunc)EnChristmasTree_Init, + (ActorFunc)EnChristmasTree_Destroy, + (ActorFunc)EnChristmasTree_Update, + (ActorFunc)EnChristmasTree_Draw, + nullptr, +}; +extern "C" s16 gEnChristmasTreeId; + +#include "src/overlays/actors/ovl_En_ChristmasDeco/z_en_christmasdeco.h" +static ActorDBInit EnChristmasDecoInit = { + "En_ChristmasDeco", + "Christmas Decos", + ACTORCAT_PROP, + (ACTOR_FLAG_DRAW_CULLING_DISABLED), + OBJECT_GAMEPLAY_KEEP, + sizeof(EnChristmasDeco), + (ActorFunc)EnChristmasDeco_Init, + (ActorFunc)EnChristmasDeco_Destroy, + (ActorFunc)EnChristmasDeco_Update, + (ActorFunc)EnChristmasDeco_Draw, + nullptr, +}; +extern "C" s16 gEnChristmasDecoId; + void ActorDB::AddBuiltInCustomActors() { gEnPartnerId = ActorDB::Instance->AddEntry(EnPartnerInit).entry.id; + gEnSnowballId = ActorDB::Instance->AddEntry(EnSnowballInit).entry.id; + gEnChristmasTreeId = ActorDB::Instance->AddEntry(EnChristmasTreeInit).entry.id; + gEnChristmasDecoId = ActorDB::Instance->AddEntry(EnChristmasDecoInit).entry.id; } extern "C" ActorDBEntry* ActorDB_Retrieve(const int id) { diff --git a/soh/soh/Enhancements/ArrowCycle.cpp b/soh/soh/Enhancements/ArrowCycle.cpp new file mode 100644 index 00000000000..ceda16670f4 --- /dev/null +++ b/soh/soh/Enhancements/ArrowCycle.cpp @@ -0,0 +1,283 @@ +#include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h" +#include "soh/ShipInit.hpp" + +extern "C" { +#include "macros.h" +#include "variables.h" +#include "functions.h" +#include "overlays/actors/ovl_En_Arrow/z_en_arrow.h" + +s32 func_808351D4(Player* thisx, PlayState* play); // Arrow nocked +s32 func_808353D8(Player* thisx, PlayState* play); // Aiming in first person +void Player_InitItemAction(PlayState* play, Player* thisx, PlayerItemAction itemAction); + +extern PlayState* gPlayState; +} + +#define CVAR_ARROW_CYCLE_NAME CVAR_ENHANCEMENT("BowArrowCycle") +#define CVAR_ARROW_CYCLE_DEFAULT 0 +#define CVAR_ARROW_CYCLE_VALUE CVarGetInteger(CVAR_ARROW_CYCLE_NAME, CVAR_ARROW_CYCLE_DEFAULT) + +static const s16 sMagicArrowCosts[] = { 4, 4, 8 }; + +#define MINIGAME_STATUS_ACTIVE 1 + +static const s16 BUTTON_FLASH_DURATION = 3; +static const s16 BUTTON_FLASH_COUNT = 3; +static const s16 BUTTON_HIGHLIGHT_ALPHA = 128; + +static s16 sButtonFlashTimer = 0; +static s16 sButtonFlashCount = 0; + +static const PlayerItemAction sArrowCycleOrder[] = { + PLAYER_IA_BOW, + PLAYER_IA_BOW_FIRE, + PLAYER_IA_BOW_ICE, + PLAYER_IA_BOW_LIGHT, +}; + +static bool IsHoldingBow(Player* player) { + return player->heldItemAction >= PLAYER_IA_BOW && player->heldItemAction <= PLAYER_IA_BOW_LIGHT; +} + +static bool IsHoldingMagicBow(Player* player) { + return player->heldItemAction >= PLAYER_IA_BOW_FIRE && player->heldItemAction <= PLAYER_IA_BOW_LIGHT; +} + +static bool IsAimingBow(Player* player) { + return IsHoldingBow(player) && ((player->unk_6AD == 2) || (player->upperActionFunc == func_808351D4)); +} + +static bool HasArrowType(PlayerItemAction itemAction) { + switch (itemAction) { + case PLAYER_IA_BOW: + return true; + case PLAYER_IA_BOW_FIRE: + return (INV_CONTENT(ITEM_ARROW_FIRE) == ITEM_ARROW_FIRE); + case PLAYER_IA_BOW_ICE: + return (INV_CONTENT(ITEM_ARROW_ICE) == ITEM_ARROW_ICE); + case PLAYER_IA_BOW_LIGHT: + return (INV_CONTENT(ITEM_ARROW_LIGHT) == ITEM_ARROW_LIGHT); + default: + return false; + } +} + +static s32 GetBowItemForArrow(PlayerItemAction itemAction) { + switch (itemAction) { + case PLAYER_IA_BOW_FIRE: + return ITEM_BOW_ARROW_FIRE; + case PLAYER_IA_BOW_ICE: + return ITEM_BOW_ARROW_ICE; + case PLAYER_IA_BOW_LIGHT: + return ITEM_BOW_ARROW_LIGHT; + default: + return ITEM_BOW; + } +} + +static bool CanCycleArrows() { + Player* player = GET_PLAYER(gPlayState); + + // don't allow cycling during minigames + if (gSaveContext.minigameState == MINIGAME_STATUS_ACTIVE) { + return false; + } + + return !(player->stateFlags1 & PLAYER_STATE1_ON_HORSE) && player->rideActor == NULL && + INV_CONTENT(SLOT_BOW) == ITEM_BOW && + (INV_CONTENT(ITEM_ARROW_FIRE) == ITEM_ARROW_FIRE || INV_CONTENT(ITEM_ARROW_ICE) == ITEM_ARROW_ICE || + INV_CONTENT(ITEM_ARROW_LIGHT) == ITEM_ARROW_LIGHT); +} + +static s8 GetNextArrowType(s8 currentArrowType) { + int currentIndex = 0; + for (int i = 0; i < (int)ARRAY_COUNT(sArrowCycleOrder); i++) { + if (sArrowCycleOrder[i] == currentArrowType) { + currentIndex = i; + break; + } + } + + for (int offset = 1; offset <= (int)ARRAY_COUNT(sArrowCycleOrder); offset++) { + int nextIndex = (currentIndex + offset) % ARRAY_COUNT(sArrowCycleOrder); + if (HasArrowType(sArrowCycleOrder[nextIndex])) { + return sArrowCycleOrder[nextIndex]; + } + } + + return PLAYER_IA_BOW; +} + +static void UpdateButtonAlpha(s16 flashAlpha, bool isButtonBow, u16* buttonAlpha) { + if (isButtonBow) { + *buttonAlpha = flashAlpha; + if (sButtonFlashTimer == 0) { + *buttonAlpha = 255; + } + } +} + +static void UpdateFlashEffect(PlayState* play) { + if (sButtonFlashTimer <= 0) { + return; + } + + sButtonFlashTimer--; + s16 flashAlpha = (sButtonFlashTimer % 3) ? BUTTON_HIGHLIGHT_ALPHA : 255; + + if (sButtonFlashTimer == 0 && sButtonFlashCount < BUTTON_FLASH_COUNT - 1) { + sButtonFlashTimer = BUTTON_FLASH_DURATION; + sButtonFlashCount++; + } + UpdateButtonAlpha(flashAlpha, + (gSaveContext.equips.buttonItems[1] == ITEM_BOW) || + (gSaveContext.equips.buttonItems[1] >= ITEM_BOW_ARROW_FIRE && + gSaveContext.equips.buttonItems[1] <= ITEM_BOW_ARROW_LIGHT), + &play->interfaceCtx.cLeftAlpha); + + UpdateButtonAlpha(flashAlpha, + (gSaveContext.equips.buttonItems[2] == ITEM_BOW) || + (gSaveContext.equips.buttonItems[2] >= ITEM_BOW_ARROW_FIRE && + gSaveContext.equips.buttonItems[2] <= ITEM_BOW_ARROW_LIGHT), + &play->interfaceCtx.cDownAlpha); + + UpdateButtonAlpha(flashAlpha, + (gSaveContext.equips.buttonItems[3] == ITEM_BOW) || + (gSaveContext.equips.buttonItems[3] >= ITEM_BOW_ARROW_FIRE && + gSaveContext.equips.buttonItems[3] <= ITEM_BOW_ARROW_LIGHT), + &play->interfaceCtx.cRightAlpha); + + if (CVarGetInteger(CVAR_ENHANCEMENT("DpadEquips"), 0)) { + UpdateButtonAlpha(flashAlpha, + (gSaveContext.equips.buttonItems[4] == ITEM_BOW) || + (gSaveContext.equips.buttonItems[4] >= ITEM_BOW_ARROW_FIRE && + gSaveContext.equips.buttonItems[4] <= ITEM_BOW_ARROW_LIGHT), + &play->interfaceCtx.dpadRightAlpha); + + UpdateButtonAlpha(flashAlpha, + (gSaveContext.equips.buttonItems[5] == ITEM_BOW) || + (gSaveContext.equips.buttonItems[5] >= ITEM_BOW_ARROW_FIRE && + gSaveContext.equips.buttonItems[5] <= ITEM_BOW_ARROW_LIGHT), + &play->interfaceCtx.dpadLeftAlpha); + + UpdateButtonAlpha(flashAlpha, + (gSaveContext.equips.buttonItems[6] == ITEM_BOW) || + (gSaveContext.equips.buttonItems[6] >= ITEM_BOW_ARROW_FIRE && + gSaveContext.equips.buttonItems[6] <= ITEM_BOW_ARROW_LIGHT), + &play->interfaceCtx.dpadDownAlpha); + + UpdateButtonAlpha(flashAlpha, + (gSaveContext.equips.buttonItems[7] == ITEM_BOW) || + (gSaveContext.equips.buttonItems[7] >= ITEM_BOW_ARROW_FIRE && + gSaveContext.equips.buttonItems[7] <= ITEM_BOW_ARROW_LIGHT), + &play->interfaceCtx.dpadUpAlpha); + } +} + +static void UpdateEquippedBow(PlayState* play, s8 arrowType) { + s32 bowItem = GetBowItemForArrow((PlayerItemAction)arrowType); + bool dpadEnabled = CVarGetInteger(CVAR_ENHANCEMENT("DpadEquips"), 0); + s32 maxButton = dpadEnabled ? 7 : 3; + + for (s32 i = 1; i <= maxButton; i++) { + if ((gSaveContext.equips.buttonItems[i] == ITEM_BOW) || + (gSaveContext.equips.buttonItems[i] >= ITEM_BOW_ARROW_FIRE && + gSaveContext.equips.buttonItems[i] <= ITEM_BOW_ARROW_LIGHT)) { + gSaveContext.equips.buttonItems[i] = bowItem; + gSaveContext.equips.cButtonSlots[i - 1] = SLOT_BOW; + + if (i <= 3) { + Interface_LoadItemIcon1(play, i); + } + + gSaveContext.buttonStatus[i] = BTN_ENABLED; + sButtonFlashTimer = BUTTON_FLASH_DURATION; + sButtonFlashCount = 0; + } + } + + UpdateFlashEffect(play); +} + +static void CycleToNextArrow(PlayState* play, Player* player) { + s8 nextArrow = GetNextArrowType(player->heldItemAction); + + if (player->heldActor != NULL && player->heldActor->id == ACTOR_EN_ARROW) { + EnArrow* arrow = (EnArrow*)player->heldActor; + + if (arrow->actor.child != NULL) { + Actor_Kill(arrow->actor.child); + } + + Actor_Kill(&arrow->actor); + } + + Player_InitItemAction(play, player, (PlayerItemAction)nextArrow); + UpdateEquippedBow(play, nextArrow); + Audio_PlaySoundGeneral(NA_SE_PL_CHANGE_ARMS, &gSfxDefaultPos, 4, &gSfxDefaultFreqAndVolScale, + &gSfxDefaultFreqAndVolScale, &gSfxDefaultReverb); +} + +void ArrowCycleMain() { + if (gPlayState == nullptr || !CanCycleArrows()) { + return; + } + + UpdateFlashEffect(gPlayState); + + Player* player = GET_PLAYER(gPlayState); + Input* input = &gPlayState->state.input[0]; + + if (IsAimingBow(player) && CHECK_BTN_ANY(input->press.button, BTN_R)) { + if (IsHoldingMagicBow(player) && gSaveContext.magicState != MAGIC_STATE_IDLE && player->heldActor == NULL) { + Audio_PlaySoundGeneral(NA_SE_SY_ERROR, &gSfxDefaultPos, 4, &gSfxDefaultFreqAndVolScale, + &gSfxDefaultFreqAndVolScale, &gSfxDefaultReverb); + return; + } + + // reset magic state to IDLE before cycling to prevent error sound + gSaveContext.magicState = MAGIC_STATE_IDLE; + + CycleToNextArrow(gPlayState, player); + } +} + +void RegisterArrowCycle() { + COND_ID_HOOK(OnActorUpdate, ACTOR_PLAYER, CVAR_ARROW_CYCLE_VALUE, [](void* actor) { ArrowCycleMain(); }); + + // suppress shield input when R is held while aiming to allow arrow cycling + COND_VB_SHOULD(VB_EXECUTE_PLAYER_ACTION_FUNC, CVAR_ARROW_CYCLE_VALUE, { + Player* player = (Player*)va_arg(args, void*); + Input* input = (Input*)va_arg(args, void*); + if (IsAimingBow(player) && CHECK_BTN_ANY(input->cur.button, BTN_R)) { + *should = false; + } + }); + + // don't consume magic on draw, but check if we have enough to fire + COND_VB_SHOULD(VB_PLAYER_ARROW_MAGIC_CONSUMPTION, CVAR_ARROW_CYCLE_VALUE, { + Player* player = va_arg(args, Player*); + int32_t magicArrowType = va_arg(args, int32_t); + int32_t* arrowType = va_arg(args, int32_t*); + + if (gSaveContext.magic < sMagicArrowCosts[magicArrowType]) { + *arrowType = ARROW_NORMAL; + } + + *should = false; + }); + + COND_VB_SHOULD(VB_EN_ARROW_MAGIC_CONSUMPTION, CVAR_ARROW_CYCLE_VALUE, { + EnArrow* arrow = va_arg(args, EnArrow*); + + if (arrow->actor.params < ARROW_FIRE || arrow->actor.params > ARROW_LIGHT) { + return; + } + + int32_t magicArrowType = arrow->actor.params - ARROW_FIRE; + Magic_RequestChange(gPlayState, sMagicArrowCosts[magicArrowType], MAGIC_CONSUME_NOW); + }); +} + +static RegisterShipInitFunc initFunc(RegisterArrowCycle, { CVAR_ARROW_CYCLE_NAME }); diff --git a/soh/soh/Enhancements/ExtraModes/BounceOffWalls.cpp b/soh/soh/Enhancements/ExtraModes/BounceOffWalls.cpp new file mode 100644 index 00000000000..8d1643d8d67 --- /dev/null +++ b/soh/soh/Enhancements/ExtraModes/BounceOffWalls.cpp @@ -0,0 +1,24 @@ +#include +#include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h" +#include "soh/ShipInit.hpp" + +extern "C" { +#include "macros.h" +#include "functions.h" +extern PlayState* gPlayState; +} + +#define CVAR_BOUNCE_OFF_WALLS_NAME CVAR_ENHANCEMENT("BounceOffWalls") +#define CVAR_BOUNCE_OFF_WALLS_VALUE CVarGetInteger(CVAR_BOUNCE_OFF_WALLS_NAME, 0) + +static RegisterShipInitFunc initFunc( + []() { + COND_HOOK(OnPlayerUpdate, CVAR_BOUNCE_OFF_WALLS_VALUE, []() { + Player* player = GET_PLAYER(gPlayState); + if (player->actor.bgCheckFlags & 0x08 && ABS(player->linearVelocity) > 15.0f) { + player->yaw = ((player->actor.wallYaw - player->yaw) + player->actor.wallYaw) - 0x8000; + Player_PlaySfx(&player->actor, NA_SE_PL_BODY_HIT); + } + }); + }, + { CVAR_BOUNCE_OFF_WALLS_NAME }); diff --git a/soh/soh/Enhancements/ExtraTraps.cpp b/soh/soh/Enhancements/ExtraTraps.cpp index 155b1af713e..c6db2dae7fe 100644 --- a/soh/soh/Enhancements/ExtraTraps.cpp +++ b/soh/soh/Enhancements/ExtraTraps.cpp @@ -4,7 +4,8 @@ #include "soh/Enhancements/randomizer/3drando/random.hpp" #include "soh/Notification/Notification.h" #include "soh/OTRGlobals.h" - +#include "soh/Enhancements/SkipGIAnimations.h" +#include "soh/Enhancements/mods.h" extern "C" { #include "variables.h" #include "functions.h" @@ -17,6 +18,10 @@ GetItemEntry ItemTable_RetrieveEntry(s16 modIndex, s16 getItemID); #define CVAR_EXTRA_TRAPS_DEFAULT 0 #define CVAR_EXTRA_TRAPS_VALUE CVarGetInteger(CVAR_EXTRA_TRAPS_NAME, CVAR_EXTRA_TRAPS_DEFAULT) +#define CVAR_TELEPORT_MODE_NAME CVAR_ENHANCEMENT("ExtraTraps.TeleportMode") +#define TELEPORT_MODE_SIMPLE 0 +#define TELEPORT_MODE_ADVANCED 1 + typedef enum { ADD_ICE_TRAP, ADD_BURN_TRAP, @@ -26,6 +31,7 @@ typedef enum { ADD_BOMB_TRAP, ADD_VOID_TRAP, ADD_AMMO_TRAP, + ADD_CHANGE_AGE_TRAP, ADD_KILL_TRAP, ADD_TELEPORT_TRAP, ADD_TRAP_MAX @@ -36,11 +42,214 @@ static int statusTimer = -1; static int eventTimer = -1; const char* altTrapTypeCvars[] = { - CVAR_ENHANCEMENT("ExtraTraps.Ice"), CVAR_ENHANCEMENT("ExtraTraps.Burn"), - CVAR_ENHANCEMENT("ExtraTraps.Shock"), CVAR_ENHANCEMENT("ExtraTraps.Knockback"), - CVAR_ENHANCEMENT("ExtraTraps.Speed"), CVAR_ENHANCEMENT("ExtraTraps.Bomb"), - CVAR_ENHANCEMENT("ExtraTraps.Void"), CVAR_ENHANCEMENT("ExtraTraps.Ammo"), - CVAR_ENHANCEMENT("ExtraTraps.Kill"), CVAR_ENHANCEMENT("ExtraTraps.Teleport"), + CVAR_ENHANCEMENT("ExtraTraps.Ice"), // ADD_ICE_TRAP + CVAR_ENHANCEMENT("ExtraTraps.Burn"), // ADD_BURN_TRAP + CVAR_ENHANCEMENT("ExtraTraps.Shock"), // ADD_SHOCK_TRAP + CVAR_ENHANCEMENT("ExtraTraps.Knockback"), // ADD_KNOCK_TRAP + CVAR_ENHANCEMENT("ExtraTraps.Speed"), // ADD_SPEED_TRAP + CVAR_ENHANCEMENT("ExtraTraps.Bomb"), // ADD_BOMB_TRAP + CVAR_ENHANCEMENT("ExtraTraps.Void"), // ADD_VOID_TRAP + CVAR_ENHANCEMENT("ExtraTraps.Ammo"), // ADD_AMMO_TRAP + CVAR_ENHANCEMENT("ExtraTraps.ChangeAge"), // ADD_CHANGE_AGE_TRAP + CVAR_ENHANCEMENT("ExtraTraps.Kill"), // ADD_KILL_TRAP + CVAR_ENHANCEMENT("ExtraTraps.Teleport"), // ADD_TELEPORT_TRAP +}; + +// Advanced Teleport Entrances +static const std::vector sTeleportTrapEntrances = { + ENTR_DEKU_TREE_ENTRANCE, + ENTR_KOKIRI_FOREST_OUTSIDE_DEKU_TREE, + ENTR_DODONGOS_CAVERN_ENTRANCE, + ENTR_DEATH_MOUNTAIN_TRAIL_OUTSIDE_DODONGOS_CAVERN, + ENTR_JABU_JABU_ENTRANCE, + ENTR_ZORAS_FOUNTAIN_OUTSIDE_JABU_JABU, + ENTR_FOREST_TEMPLE_ENTRANCE, + ENTR_SACRED_FOREST_MEADOW_OUTSIDE_TEMPLE, + ENTR_FIRE_TEMPLE_ENTRANCE, + ENTR_DEATH_MOUNTAIN_CRATER_OUTSIDE_TEMPLE, + ENTR_WATER_TEMPLE_ENTRANCE, + ENTR_LAKE_HYLIA_OUTSIDE_TEMPLE, + ENTR_SPIRIT_TEMPLE_ENTRANCE, + ENTR_DESERT_COLOSSUS_OUTSIDE_TEMPLE, + ENTR_SHADOW_TEMPLE_ENTRANCE, + ENTR_GRAVEYARD_OUTSIDE_TEMPLE, + ENTR_BOTTOM_OF_THE_WELL_ENTRANCE, + ENTR_KAKARIKO_VILLAGE_OUTSIDE_BOTTOM_OF_THE_WELL, + ENTR_ICE_CAVERN_ENTRANCE, + ENTR_ZORAS_FOUNTAIN_OUTSIDE_ICE_CAVERN, + ENTR_GERUDO_TRAINING_GROUND_ENTRANCE, + ENTR_GERUDOS_FORTRESS_OUTSIDE_GERUDO_TRAINING_GROUND, + ENTR_INSIDE_GANONS_CASTLE_ENTRANCE, + ENTR_CASTLE_GROUNDS_RAINBOW_BRIDGE_EXIT, + ENTR_MIDOS_HOUSE_0, + ENTR_KOKIRI_FOREST_OUTSIDE_MIDOS_HOUSE, + ENTR_SARIAS_HOUSE_0, + ENTR_KOKIRI_FOREST_OUTSIDE_SARIAS_HOUSE, + ENTR_TWINS_HOUSE_0, + ENTR_KOKIRI_FOREST_OUTSIDE_TWINS_HOUSE, + ENTR_KNOW_IT_ALL_BROS_HOUSE_0, + ENTR_KOKIRI_FOREST_OUTSIDE_KNOW_IT_ALL_HOUSE, + ENTR_KOKIRI_SHOP_0, + ENTR_KOKIRI_FOREST_OUTSIDE_SHOP, + ENTR_LAKESIDE_LABORATORY_0, + ENTR_LAKE_HYLIA_OUTSIDE_LAB, + ENTR_FISHING_POND_0, + ENTR_LAKE_HYLIA_OUTSIDE_FISHING_POND, + ENTR_CARPENTERS_TENT_0, + ENTR_GERUDO_VALLEY_OUTSIDE_TENT, + ENTR_MARKET_GUARD_HOUSE_0, + ENTR_MARKET_ENTRANCE_OUTSIDE_GUARD_HOUSE, + ENTR_HAPPY_MASK_SHOP_0, + ENTR_MARKET_DAY_OUTSIDE_HAPPY_MASK_SHOP, + ENTR_BOMBCHU_BOWLING_ALLEY_0, + ENTR_MARKET_DAY_OUTSIDE_BOMBCHU_BOWLING, + ENTR_POTION_SHOP_MARKET_0, + ENTR_MARKET_DAY_OUTSIDE_POTION_SHOP, + ENTR_TREASURE_BOX_SHOP_0, + ENTR_MARKET_DAY_OUTSIDE_TREASURE_BOX_SHOP, + ENTR_BOMBCHU_SHOP_1, + ENTR_BACK_ALLEY_DAY_OUTSIDE_BOMBCHU_SHOP, + ENTR_BACK_ALLEY_MAN_IN_GREEN_HOUSE, + ENTR_BACK_ALLEY_DAY_OUTSIDE_MAN_IN_GREEN_HOUSE, + ENTR_KAKARIKO_CENTER_GUEST_HOUSE_0, + ENTR_KAKARIKO_VILLAGE_OUTSIDE_CENTER_GUEST_HOUSE, + ENTR_HOUSE_OF_SKULLTULA_0, + ENTR_KAKARIKO_VILLAGE_OUTSIDE_SKULKLTULA_HOUSE, + ENTR_IMPAS_HOUSE_FRONT, + ENTR_KAKARIKO_VILLAGE_OUTSIDE_IMPAS_HOUSE_FRONT, + ENTR_IMPAS_HOUSE_BACK, + ENTR_KAKARIKO_VILLAGE_OUTSIDE_IMPAS_HOUSE_BACK, + ENTR_POTION_SHOP_GRANNY_0, + ENTR_KAKARIKO_VILLAGE_OUTSIDE_SHOP_GRANNY, + ENTR_GRAVEKEEPERS_HUT_0, + ENTR_GRAVEYARD_OUTSIDE_DAMPES_HUT, + ENTR_GORON_SHOP_0, + ENTR_GORON_CITY_OUTSIDE_SHOP, + ENTR_ZORA_SHOP_0, + ENTR_ZORAS_DOMAIN_OUTSIDE_SHOP, + ENTR_LON_LON_BUILDINGS_TALONS_HOUSE, + ENTR_LON_LON_RANCH_OUTSIDE_TALONS_HOUSE, + ENTR_STABLE_0, + ENTR_LON_LON_RANCH_OUTSIDE_STABLES, + ENTR_LON_LON_BUILDINGS_TOWER, + ENTR_LON_LON_RANCH_OUTSIDE_TOWER, + ENTR_BAZAAR_1, + ENTR_MARKET_DAY_OUTSIDE_BAZAAR, + ENTR_SHOOTING_GALLERY_1, + ENTR_MARKET_DAY_OUTSIDE_SHOOTING_GALLERY, + ENTR_BAZAAR_0, + ENTR_KAKARIKO_VILLAGE_OUTSIDE_BAZAAR, + ENTR_SHOOTING_GALLERY_0, + ENTR_KAKARIKO_VILLAGE_OUTSIDE_SHOOTING_GALLERY, + ENTR_GREAT_FAIRYS_FOUNTAIN_SPELLS_NAYRUS_COLOSSUS, + ENTR_DESERT_COLOSSUS_GREAT_FAIRY_EXIT, + ENTR_GREAT_FAIRYS_FOUNTAIN_SPELLS_DINS_HC, + ENTR_CASTLE_GROUNDS_GREAT_FAIRY_EXIT, + ENTR_GREAT_FAIRYS_FOUNTAIN_MAGIC_OGC_DD, + ENTR_POTION_SHOP_KAKARIKO_1, + ENTR_GREAT_FAIRYS_FOUNTAIN_MAGIC_DMC, + ENTR_DEATH_MOUNTAIN_CRATER_GREAT_FAIRY_EXIT, + ENTR_GREAT_FAIRYS_FOUNTAIN_MAGIC_DMT, + ENTR_DEATH_MOUNTAIN_TRAIL_GREAT_FAIRY_EXIT, + ENTR_GREAT_FAIRYS_FOUNTAIN_SPELLS_FARORES_ZF, + ENTR_ZORAS_FOUNTAIN_OUTSIDE_GREAT_FAIRY, + ENTR_LINKS_HOUSE_1, + ENTR_KOKIRI_FOREST_OUTSIDE_LINKS_HOUSE, + ENTR_TEMPLE_OF_TIME_ENTRANCE, + ENTR_TEMPLE_OF_TIME_EXTERIOR_DAY_OUTSIDE_TEMPLE, + ENTR_WINDMILL_AND_DAMPES_GRAVE_WINDMILL, + ENTR_KAKARIKO_VILLAGE_OUTSIDE_WINDMILL, + ENTR_POTION_SHOP_KAKARIKO_FRONT, + ENTR_KAKARIKO_VILLAGE_OUTSIDE_POTION_SHOP_FRONT, + ENTR_POTION_SHOP_KAKARIKO_BACK, + ENTR_KAKARIKO_VILLAGE_OUTSIDE_POTION_SHOP_BACK, + ENTR_GRAVE_WITH_FAIRYS_FOUNTAIN_0, + ENTR_GRAVEYARD_SHIELD_GRAVE_EXIT, + ENTR_REDEAD_GRAVE_0, + ENTR_GRAVEYARD_HEART_PIECE_GRAVE_EXIT, + ENTR_ROYAL_FAMILYS_TOMB_0, + ENTR_GRAVEYARD_ROYAL_TOMB_EXIT, + ENTR_WINDMILL_AND_DAMPES_GRAVE_GRAVE, + ENTR_GRAVEYARD_DAMPES_GRAVE_EXIT, + ENTR_LOST_WOODS_BRIDGE_EAST_EXIT, + ENTR_KOKIRI_FOREST_LOWER_EXIT, + ENTR_LOST_WOODS_SOUTH_EXIT, + ENTR_KOKIRI_FOREST_UPPER_EXIT, + ENTR_GORON_CITY_TUNNEL_SHORTCUT, + ENTR_LOST_WOODS_TUNNEL_SHORTCUT, + ENTR_ZORAS_RIVER_UNDERWATER_SHORTCUT, + ENTR_LOST_WOODS_UNDERWATER_SHORTCUT, + ENTR_SACRED_FOREST_MEADOW_SOUTH_EXIT, + ENTR_LOST_WOODS_NORTH_EXIT, + ENTR_HYRULE_FIELD_WOODED_EXIT, + ENTR_LOST_WOODS_BRIDGE_WEST_EXIT, + ENTR_LAKE_HYLIA_NORTH_EXIT, + ENTR_HYRULE_FIELD_FENCE_EXIT, + ENTR_GERUDO_VALLEY_EAST_EXIT, + ENTR_HYRULE_FIELD_ROCKY_PATH, + ENTR_MARKET_ENTRANCE_NEAR_GUARD_EXIT, + ENTR_HYRULE_FIELD_ON_BRIDGE_SPAWN, + ENTR_KAKARIKO_VILLAGE_FRONT_GATE, + ENTR_HYRULE_FIELD_STAIRS_EXIT, + ENTR_ZORAS_RIVER_WEST_EXIT, + ENTR_HYRULE_FIELD_RIVER_EXIT, + ENTR_LON_LON_RANCH_ENTRANCE, + ENTR_HYRULE_FIELD_CENTER_EXIT, + ENTR_ZORAS_DOMAIN_UNDERWATER_SHORTCUT, + ENTR_LAKE_HYLIA_UNDERWATER_SHORTCUT, + ENTR_GERUDOS_FORTRESS_EAST_EXIT, + ENTR_GERUDO_VALLEY_WEST_EXIT, + ENTR_HAUNTED_WASTELAND_EAST_EXIT, + ENTR_GERUDOS_FORTRESS_GATE_EXIT, + ENTR_DESERT_COLOSSUS_EAST_EXIT, + ENTR_HAUNTED_WASTELAND_WEST_EXIT, + ENTR_MARKET_SOUTH_EXIT, + ENTR_MARKET_ENTRANCE_NORTH_EXIT, + ENTR_CASTLE_GROUNDS_SOUTH_EXIT, + ENTR_MARKET_DAY_CASTLE_EXIT, + ENTR_TEMPLE_OF_TIME_EXTERIOR_DAY_GOSSIP_STONE_EXIT, + ENTR_MARKET_DAY_TEMPLE_EXIT, + ENTR_GRAVEYARD_ENTRANCE, + ENTR_KAKARIKO_VILLAGE_SOUTHEAST_EXIT, + ENTR_DEATH_MOUNTAIN_TRAIL_BOTTOM_EXIT, + ENTR_KAKARIKO_VILLAGE_GUARD_GATE, + ENTR_GORON_CITY_UPPER_EXIT, + ENTR_DEATH_MOUNTAIN_TRAIL_GC_EXIT, + ENTR_DEATH_MOUNTAIN_CRATER_GC_EXIT, + ENTR_GORON_CITY_DARUNIA_ROOM_EXIT, + ENTR_DEATH_MOUNTAIN_CRATER_UPPER_EXIT, + ENTR_DEATH_MOUNTAIN_TRAIL_SUMMIT_EXIT, + ENTR_ZORAS_DOMAIN_ENTRANCE, + ENTR_ZORAS_RIVER_WATERFALL_EXIT, + ENTR_ZORAS_FOUNTAIN_TUNNEL_EXIT, + ENTR_ZORAS_DOMAIN_KING_ZORA_EXIT, + ENTR_LAKE_HYLIA_RIVER_EXIT, + ENTR_HYRULE_FIELD_OWL_DROP, + ENTR_KAKARIKO_VILLAGE_OWL_DROP, + ENTR_LINKS_HOUSE_CHILD_SPAWN, + ENTR_HYRULE_FIELD_10, + ENTR_SACRED_FOREST_MEADOW_WARP_PAD, + ENTR_DEATH_MOUNTAIN_CRATER_WARP_PAD, + ENTR_LAKE_HYLIA_WARP_PAD, + ENTR_DESERT_COLOSSUS_WARP_PAD, + ENTR_GRAVEYARD_WARP_PAD, + ENTR_TEMPLE_OF_TIME_WARP_PAD, + ENTR_DEKU_TREE_BOSS_ENTRANCE, + ENTR_DEKU_TREE_BOSS_DOOR, + ENTR_DODONGOS_CAVERN_BOSS_ENTRANCE, + ENTR_DODONGOS_CAVERN_BOSS_DOOR, + ENTR_JABU_JABU_BOSS_ENTRANCE, + ENTR_JABU_JABU_BOSS_DOOR, + ENTR_FOREST_TEMPLE_BOSS_ENTRANCE, + ENTR_FOREST_TEMPLE_BOSS_DOOR, + ENTR_FIRE_TEMPLE_BOSS_ENTRANCE, + ENTR_FIRE_TEMPLE_BOSS_DOOR, + ENTR_WATER_TEMPLE_BOSS_ENTRANCE, + ENTR_WATER_TEMPLE_BOSS_DOOR, + ENTR_SPIRIT_TEMPLE_BOSS_ENTRANCE, + ENTR_SPIRIT_TEMPLE_BOSS_DOOR, + ENTR_SHADOW_TEMPLE_BOSS_ENTRANCE, + ENTR_SHADOW_TEMPLE_BOSS_DOOR, }; std::vector getEnabledAddTraps() { @@ -60,9 +269,58 @@ std::vector getEnabledAddTraps() { return enabledAddTraps; }; +static void DoTeleportTrapSimple() { + int entrance; + int index = Random(0, 7); + + switch (index) { + case 0: + entrance = GI_TP_DEST_SERENADE; + break; + case 1: + entrance = GI_TP_DEST_REQUIEM; + break; + case 2: + entrance = GI_TP_DEST_BOLERO; + break; + case 3: + entrance = GI_TP_DEST_MINUET; + break; + case 4: + entrance = GI_TP_DEST_NOCTURNE; + break; + case 5: + entrance = GI_TP_DEST_PRELUDE; + break; + default: + entrance = GI_TP_DEST_LINKSHOUSE; + break; + } + + GameInteractor::RawAction::TeleportPlayer(entrance); +} + +static void DoTeleportTrapAdvanced() { + if (sTeleportTrapEntrances.empty()) { + // Paranoid fallback + DoTeleportTrapSimple(); + return; + } + + uint32_t entrance = RandomElement(sTeleportTrapEntrances); + + // Use the same helper as Simple so we get the laugh + proper transition handling + GameInteractor::RawAction::TeleportPlayer(static_cast(entrance)); +} + static void RollRandomTrap(uint32_t seed) { uint32_t finalSeed = seed + (IS_RANDO ? Rando::Context::GetInstance()->GetSeed() : static_cast(gSaveContext.ship.stats.fileCreatedAt)); + + if (IS_ARCHIPELAGO) { + finalSeed += rand(); + } + Random_Init(finalSeed); roll = RandomElement(getEnabledAddTraps()); @@ -98,6 +356,9 @@ static void RollRandomTrap(uint32_t seed) { eventTimer = 3; Notification::Emit({ .message = "Ammo Halved!" }); break; + case ADD_CHANGE_AGE_TRAP: + SwitchAge(); + break; case ADD_KILL_TRAP: GameInteractor::RawAction::SetPlayerHealth(0); break; @@ -136,32 +397,12 @@ static void OnPlayerUpdate() { &gSfxDefaultFreqAndVolScale, &gSfxDefaultReverb); break; case ADD_TELEPORT_TRAP: { - int entrance; - int index = Random(0, 7); - switch (index) { - case 0: - entrance = GI_TP_DEST_SERENADE; - break; - case 1: - entrance = GI_TP_DEST_REQUIEM; - break; - case 2: - entrance = GI_TP_DEST_BOLERO; - break; - case 3: - entrance = GI_TP_DEST_MINUET; - break; - case 4: - entrance = GI_TP_DEST_NOCTURNE; - break; - case 5: - entrance = GI_TP_DEST_PRELUDE; - break; - default: - entrance = GI_TP_DEST_LINKSHOUSE; - break; + int teleportMode = CVarGetInteger(CVAR_TELEPORT_MODE_NAME, TELEPORT_MODE_SIMPLE); + if (teleportMode == TELEPORT_MODE_ADVANCED) { + DoTeleportTrapAdvanced(); + } else { + DoTeleportTrapSimple(); } - GameInteractor::RawAction::TeleportPlayer(entrance); break; } default: @@ -191,7 +432,7 @@ void RegisterExtraTraps() { gSaveContext.ship.stats.count[COUNT_ICE_TRAPS]++; GameInteractor_ExecuteOnItemReceiveHooks(ItemTable_RetrieveEntry(MOD_RANDOMIZER, RG_ICE_TRAP)); if (CVAR_EXTRA_TRAPS_VALUE) { - RollRandomTrap(gPlayState->sceneNum + player->getItemEntry.drawItemId); + RollRandomTrap(gPlayState->sceneNum + static_cast(GetLastIceTrapCheck())); } else { GameInteractor::RawAction::FreezePlayer(); } diff --git a/soh/soh/Enhancements/FileSelectEnhancements.cpp b/soh/soh/Enhancements/FileSelectEnhancements.cpp index d2671f20e6a..ab2940a541a 100644 --- a/soh/soh/Enhancements/FileSelectEnhancements.cpp +++ b/soh/soh/Enhancements/FileSelectEnhancements.cpp @@ -1,6 +1,8 @@ #include "FileSelectEnhancements.h" #include "soh/OTRGlobals.h" +#include "soh/SohGui/SohModals.h" +#include "soh/SohGui/SohGui.hpp" #include #include @@ -62,6 +64,112 @@ std::array RandomizerSettingsMenuText[RSM_MAX] = { }, }; -const char* SohFileSelect_GetSettingText(uint8_t optionIndex, uint8_t language) { +std::array ArchipelagoSettingsMenuText[ASM_MAX]{ + // ASM_START_ARCHIPELAGO + { + "Start Archipelago", + "Start Archipelago", + "Start Archipelago", + }, + // ASM_CHANGE_CONNECTION_INFO + { + "Change Connection Info", + "Change Connection Info", + "Change Connection Info", + }, + // ASM_SERVER_ADDRESS + { + "Server Address: ", + "Server Address: ", + "Server Address: ", + }, + // ASM_SLOT_NAME + { + "Slot Name: ", + "Slot Name: ", + "Slot Name: ", + }, + // ASM_NOT_CONNECTED + { + "Not Connected", + "Not Connected", + "Not Connected", + }, + // ASM_CONNECTING + { + "Connecting...", + "Connecting...", + "Connecting...", + }, + // ASM_CONNECTED + { + "Connected", + "Connected", + "Connected", + }, + // ASM_STATUS + { + "Status:", + "Status:", + "Status:", + }, + // ASM_CHAR_START_TO_CONNECT + { + "Start to automatically connect to this slot", + "Start to automatically connect to this slot", + "Start to automatically connect to this slot", + }, + // ASM_CHAR_SELECT_CONNECTED_TO_OTHER_SLOT + { + "Connected to a different slot", + "Connected to a different slot", + "Connected to a different slot", + }, + // ASM_CHAR_SELECT_CHANGE_CONNECTION_INFO + { + "Z-Connection Settings", + "Z-Connection Settings", + "Z-Connection Settings", + } +}; + +const char* SohFileSelect_GetRandomizerSettingText(uint8_t optionIndex, uint8_t language) { return RandomizerSettingsMenuText[optionIndex][language].c_str(); } + +const char* SohFileSelect_GetArchipelagoSettingText(uint8_t optionIndex, uint8_t language) { + return ArchipelagoSettingsMenuText[optionIndex][language].c_str(); +} + +void SohFileSelect_ShowPresetMenu() { + SohGui::ShowEscMenu(); + CVarSetString(CVAR_SETTING("Menu.ActiveHeader"), "Settings"); + CVarSetString(CVAR_SETTING("Menu.SettingsSidebarSection"), "Presets"); + CVarSetInteger(CVAR_SETTING("Menu.HasSeenPresetModal"), 1); +} + +void SohFileSelect_DismissPresetModal() { + CVarSetInteger(CVAR_SETTING("Menu.HasSeenPresetModal"), 1); +} + +void SohFileSelect_ShowPresetModal() { + if (CVarGetInteger(CVAR_SETTING("Menu.HasSeenPresetModal"), 0)) { + return; + } + std::shared_ptr modal = static_pointer_cast( + Ship::Context::GetInstance()->GetWindow()->GetGui()->GetGuiWindow("Modal Window")); + if (modal->IsPopupOpen("Take a look at our presets!")) { + modal->DismissPopup(); + } else { + modal->RegisterPopup("Take a look at our presets!", + "\nHey there! Ship comes with a ton of options, but none of them are on by default,\n" + "even in randomizer. If you haven't already, we highly recommend applying the\n" + "\"Enhancements - Curated Randomizer\" preset for a great, curated out of the\n" + "box rando experience.\n" + "\n" + "Afterwards, consider taking a look at the rest of the ESC menu to further tweak \n" + "the experience to your liking!\n ", + "Cool, show me the presets!", "Got it, just let me play!", SohFileSelect_ShowPresetMenu, + SohFileSelect_DismissPresetModal); + } +} diff --git a/soh/soh/Enhancements/FileSelectEnhancements.h b/soh/soh/Enhancements/FileSelectEnhancements.h index 070d270dc7b..6083e67b0b6 100644 --- a/soh/soh/Enhancements/FileSelectEnhancements.h +++ b/soh/soh/Enhancements/FileSelectEnhancements.h @@ -6,7 +6,10 @@ #ifdef __cplusplus extern "C" { #endif +const char* SohFileSelect_GetRandomizerSettingText(u8 optionIndex, u8 language); +const char* SohFileSelect_GetArchipelagoSettingText(u8 optionIndex, u8 language); const char* SohFileSelect_GetSettingText(u8 optionIndex, u8 language); +void SohFileSelect_ShowPresetModal(); #ifdef __cplusplus }; #endif @@ -20,4 +23,19 @@ typedef enum { RSM_MAX, } RandomizerSettingsMenuEnums; +typedef enum { + ASM_START_ARCHIPELAGO, + ASM_CHANGE_CONNECTION_INFO, + ASM_SERVER_ADDRESS, + ASM_SLOT_NAME, + ASM_NOT_CONNECTED, + ASM_CONNECTING, + ASM_CONNECTED, + ASM_STATUS, + ASM_CHAR_START_TO_CONNECT, + ASM_CHAR_SELECT_CONNECTED_TO_OTHER_SLOT, + ASM_CHAR_SELECT_CHANGE_CONNECTION_INFO, + ASM_MAX +} ArchipelagoSettingsMenuEnums; + #endif diff --git a/soh/soh/Enhancements/GameplayStats/BossDefeatTimestamps.cpp b/soh/soh/Enhancements/GameplayStats/BossDefeatTimestamps.cpp index dff3bc91256..4a3f240f899 100644 --- a/soh/soh/Enhancements/GameplayStats/BossDefeatTimestamps.cpp +++ b/soh/soh/Enhancements/GameplayStats/BossDefeatTimestamps.cpp @@ -1,5 +1,6 @@ #include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h" #include "soh/ShipInit.hpp" +#include "soh/Network/Archipelago/Archipelago.h" extern "C" SaveContext gSaveContext; @@ -19,8 +20,10 @@ static void RegisterBossDefeatTimestamps() { BOSS_DEFEAT_TIMESTAMP(ACTOR_BOSS_GANON, TIMESTAMP_DEFEAT_GANONDORF); BOSS_DEFEAT_TIMESTAMP(ACTOR_BOSS_GANON2, TIMESTAMP_DEFEAT_GANON); - COND_ID_HOOK(OnBossDefeat, ACTOR_BOSS_GANON2, true, - [](void* refActor) { gSaveContext.ship.stats.gameComplete = true; }); + COND_ID_HOOK(OnBossDefeat, ACTOR_BOSS_GANON2, true, [](void* refActor) { + gSaveContext.ship.stats.gameComplete = true; + ArchipelagoClient::GetInstance().SendGameWon(); + }); } static RegisterShipInitFunc initFunc(RegisterBossDefeatTimestamps); diff --git a/soh/soh/Enhancements/Holiday/AGreenSpoon.cpp b/soh/soh/Enhancements/Holiday/AGreenSpoon.cpp new file mode 100644 index 00000000000..b69f2e8712f --- /dev/null +++ b/soh/soh/Enhancements/Holiday/AGreenSpoon.cpp @@ -0,0 +1,71 @@ +#include "Holiday.hpp" +#include "soh/Enhancements/randomizer/3drando/random.hpp" +#include "soh/frame_interpolation.h" +#include "soh_assets.h" +#include "overlays/actors/ovl_En_Gs/z_en_gs.h" +#include "overlays/actors/ovl_En_Clear_Tag/z_en_clear_tag.h" + +extern "C" { +#include "macros.h" +#include "functions.h" +#include "variables.h" +extern PlayState* gPlayState; +} + +#define CVAR(v) "gHoliday.Gameplay." v + +void EnGs_Evil(EnGs* enGs, PlayState* play) { + Player* player = GET_PLAYER(gPlayState); + if (!(player->stateFlags1 & PLAYER_STATE1_TALKING)) { + Math_ApproachS(&enGs->actor.shape.rot.y, enGs->actor.yawTowardsPlayer, 5, 0xBB8); + + if (enGs->unk_200 <= 0) { + float offsetDistance = 10.0f; + float offsetX = sinf(enGs->actor.shape.rot.y * (M_PI / 0x8000)) * offsetDistance; + float offsetZ = cosf(enGs->actor.shape.rot.y * (M_PI / 0x8000)) * offsetDistance; + + float dx = player->actor.world.pos.x - (enGs->actor.world.pos.x + offsetX); + float dy = player->actor.world.pos.y - 10.0f - enGs->actor.world.pos.y; + float dz = player->actor.world.pos.z - (enGs->actor.world.pos.z + offsetZ); + + s16 rotX = atan2f(dy, sqrtf(dx * dx + dz * dz)) * (0x8000 / M_PI); + s16 rotY = enGs->actor.shape.rot.y; + s16 rotZ = atan2f(dx, dz) * (0x8000 / M_PI); + + Actor* actor = Actor_Spawn(&play->actorCtx, play, ACTOR_EN_CLEAR_TAG, enGs->actor.world.pos.x + offsetX, + enGs->actor.world.pos.y + 40.0f, enGs->actor.world.pos.z + offsetZ, rotX, rotY, + rotZ, 100, false); + + EnClearTag* clearTag = (EnClearTag*)actor; + + enGs->unk_200 = 5; + } + + enGs->unk_200--; + } +} + +static void OnConfigurationChanged() { + COND_ID_HOOK( + OnOpenText, 0x2053, CVarGetInteger(CVAR("EvilGossipStone"), 0), [](u16* textId, bool* loadFromMessageTable) { + Actor* actor = + Actor_FindNearby(gPlayState, &GET_PLAYER(gPlayState)->actor, ACTOR_EN_GS, ACTORCAT_PROP, 100.0f); + + if (actor == NULL) { + return; + } + + EnGs* gs = (EnGs*)actor; + gs->actionFunc = EnGs_Evil; + }); +} + +static void RegisterMenu() { + WidgetPath path = { "Holiday", "Gameplay", SECTION_COLUMN_1 }; + SohGui::mSohMenu->AddWidget(path, "Evil Gossip Stone", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR("EvilGossipStone")) + .Options(UIWidgets::CheckboxOptions().Tooltip("Gossip stones become hostile after being spoken to.")); +} + +static RegisterShipInitFunc initFunc(OnConfigurationChanged, { CVAR("EvilGossipStone") }); +static RegisterMenuInitFunc menuInitFunc(RegisterMenu); diff --git a/soh/soh/Enhancements/Holiday/Archez.cpp b/soh/soh/Enhancements/Holiday/Archez.cpp new file mode 100644 index 00000000000..9bbf0ee0b86 --- /dev/null +++ b/soh/soh/Enhancements/Holiday/Archez.cpp @@ -0,0 +1,119 @@ +#include "Holiday.hpp" +#include "Archez.h" +#include +#include "soh/SohGui/UIWidgets.hpp" +#include "soh/Enhancements/game-interactor/GameInteractor.h" +#include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h" +#include "soh_assets.h" + +extern "C" { +#include "macros.h" +#include "functions.h" +#include "variables.h" +#include "objects/object_goroiwa/object_goroiwa.h" + +extern PlayState* gPlayState; +} + +#define CVAR(v) "gHoliday.Gameplay." v + +static bool sSkipNextLimb = false; +static bool sSkipNextSkeleton = false; + +extern "C" void SkipOverrideNextLimb() { + sSkipNextLimb = true; +} + +extern "C" void SkipOverrideNextSkeleton() { + sSkipNextSkeleton = true; +} + +extern "C" void ClearOverrideSkips() { + sSkipNextLimb = false; + sSkipNextSkeleton = false; +} + +static void OnConfigurationChanged() { + COND_VB_SHOULD(VB_DRAW_SKEL_LIMB, CVarGetInteger(CVAR("SnowGolems"), 0), { + if (!*should) { + return; + } + + if (sSkipNextLimb) { + sSkipNextLimb = false; + return; + } + + if (sSkipNextSkeleton) { + return; + } + + Gfx** gfxP = va_arg(args, Gfx**); + void* dList = va_arg(args, void*); + + *should = false; + + Gfx* gfx = *gfxP; + + Matrix_Push(); + + Matrix_Scale(0.55f, 0.55f, 0.55f, MTXMODE_APPLY); + gSPMatrix(gfx++, Matrix_NewMtx(gPlayState->state.gfxCtx, (char*)__FILE__, __LINE__), G_MTX_LOAD); + gSPDisplayList(gfx++, (Gfx*)gSnowballDL); + + Matrix_Pop(); + + gSPMatrix(gfx++, Matrix_NewMtx(gPlayState->state.gfxCtx, (char*)__FILE__, __LINE__), G_MTX_LOAD); + + *gfxP = gfx; + }); + + COND_VB_SHOULD(VB_DRAW_SKEL_FLEX_LIMB, CVarGetInteger(CVAR("SnowGolems"), 0), { + if (!*should) { + return; + } + + if (sSkipNextLimb) { + sSkipNextLimb = false; + } + + if (sSkipNextSkeleton) { + return; + } + + Gfx** gfxP = va_arg(args, Gfx**); + void* dList = va_arg(args, void*); + Mtx* mtx = va_arg(args, Mtx*); + + *should = false; + + Gfx* gfx = *gfxP; + MtxF mtxF; + + Matrix_Push(); + Matrix_MtxToMtxF(mtx, &mtxF); + Matrix_Put(&mtxF); + + Matrix_Scale(0.55f, 0.55f, 0.55f, MTXMODE_APPLY); + gSPMatrix(gfx++, Matrix_NewMtx(gPlayState->state.gfxCtx, (char*)__FILE__, __LINE__), G_MTX_LOAD); + gSPDisplayList(gfx++, (Gfx*)gSnowballDL); + + Matrix_Pop(); + + gSPMatrix(gfx++, mtx, G_MTX_LOAD); + + *gfxP = gfx; + }); +} + +static void RegisterMenu() { + WidgetPath path = { "Holiday", "Visual", SECTION_COLUMN_1 }; + SohGui::mSohMenu->AddWidget(path, "Snow Golems", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR("SnowGolems")) + .Callback([](WidgetInfo& info) { OnConfigurationChanged(); }) + .Options(UIWidgets::CheckboxOptions().Tooltip( + "Overrides most charactor skeletons with snow balls to make them look like Snow Golems")); +} + +static RegisterShipInitFunc initFunc(OnConfigurationChanged, { CVAR("SnowGolems") }); +static RegisterMenuInitFunc menuInitFunc(RegisterMenu); diff --git a/soh/soh/Enhancements/Holiday/Archez.h b/soh/soh/Enhancements/Holiday/Archez.h new file mode 100644 index 00000000000..235f9dee3cd --- /dev/null +++ b/soh/soh/Enhancements/Holiday/Archez.h @@ -0,0 +1,16 @@ +#ifndef ARCHEZ_H +#define ARCHEZ_H + +#ifdef __cplusplus +extern "C" { +#endif + +void SkipOverrideNextLimb(); +void SkipOverrideNextSkeleton(); +void ClearOverrideSkips(); + +#ifdef __cplusplus +} +#endif + +#endif // ARCHEZ_H diff --git a/soh/soh/Enhancements/Holiday/Caladius.cpp b/soh/soh/Enhancements/Holiday/Caladius.cpp new file mode 100644 index 00000000000..319eda14945 --- /dev/null +++ b/soh/soh/Enhancements/Holiday/Caladius.cpp @@ -0,0 +1,315 @@ +#include "Caladius.h" +#include "Holiday.hpp" +#include "soh/Notification/Notification.h" +#include "soh/Enhancements/gameplaystats.h" +#include "soh/Enhancements/game-interactor/GameInteractor.h" +#include "soh/Enhancements/custom-message/CustomMessageManager.h" +#include "soh/Enhancements/randomizer/randomizer.h" +#include "soh/frame_interpolation.h" +#include "soh/ObjectExtension/ActorListIndex.h" +#include "soh_assets.h" + +extern "C" { +#include "macros.h" +#include "functions.h" +#include "variables.h" +extern PlayState* gPlayState; +uint64_t GetUnixTimestamp(); +} + +#define CVAR(v) "gHoliday.Gameplay." v + +bool isFeverDisabled = false; +bool isExchangeDisabled = false; +static float fontScale = 1.0f; + +extern GetItemEntry vanillaQueuedItemEntry; + +std::vector boulderList = { ACTOR_OBJ_BOMBIWA, ACTOR_BG_ICE_SHELTER, ACTOR_EN_ISHI, ACTOR_EN_ISHI, + ACTOR_OBJ_HAMISHI }; + +std::string formatTimestampIceTrapFever(uint32_t value) { + uint32_t sec = value / 10; + uint32_t hh = sec / 3600; + uint32_t mm = (sec - hh * 3600) / 60; + uint32_t ss = sec - hh * 3600 - mm * 60; + return fmt::format("{}:{:0>2}:{:0>2}", hh, mm, ss); +} + +void OnTimeOver() { + gSaveContext.health = 0; +} + +int32_t calculateRemainingTime() { + int32_t timeRemaining = + ((gSaveContext.ship.stats.count[COUNT_ICE_TRAPS] * (CVarGetInteger(CVAR("ExtendTimer"), 0) * 600)) + + (CVarGetInteger(CVAR("StartTimer"), 0) * 600) - GAMEPLAYSTAT_TOTAL_TIME); + if (timeRemaining <= 0) { + OnTimeOver(); + timeRemaining = 0; + } + return timeRemaining; +} + +s32 ActorSnapToFloor(Actor* refActor, PlayState* play, f32 arg2) { + CollisionPoly* poly; + Vec3f pos; + s32 bgId; + f32 floorY; + + pos.x = refActor->world.pos.x; + pos.y = refActor->world.pos.y + 30.0f; + pos.z = refActor->world.pos.z; + floorY = BgCheck_EntityRaycastFloor4(&play->colCtx, &poly, &bgId, refActor, &pos); + if (floorY > BGCHECK_Y_MIN) { + refActor->world.pos.y = floorY + arg2; + Math_Vec3f_Copy(&refActor->home.pos, &refActor->world.pos); + } + return refActor->world.pos.y; +} + +void RandomizeBoulder(Actor* refActor) { + Actor* actor = (Actor*)refActor; + int16_t param = 0; + int32_t yAdj = 0; + + int32_t seed = gPlayState->sceneNum + actor->id + ((int32_t)(actor->world.pos.x * 10)) + + ((int32_t)(actor->world.pos.y * 10)) + ((int32_t)(actor->world.pos.z * 10)) + actor->params; + + uint32_t finalSeed = + ABS(seed) + (IS_RANDO ? Rando::Context::GetInstance()->GetSeed() : gSaveContext.ship.stats.fileCreatedAt); + Random_Init(finalSeed); + uint32_t roll = Random(0, boulderList.size()); + + u32 flag = actor->id == ACTOR_EN_ISHI ? ((actor->params >> 0xA) & 0x3C) | ((actor->params >> 6) & 3) + : actor->params & 0x3F; + + if (boulderList[roll] == ACTOR_EN_ISHI) { + param = (Random(0, 2)) | ((flag & 0x3C) << 10) | ((flag & 3) << 6); + } else { + param = flag; + } + + yAdj = ActorSnapToFloor(actor, gPlayState, 0.0f); + + Actor_Spawn(&gPlayState->actorCtx, gPlayState, boulderList[roll], actor->world.pos.x, + ActorSnapToFloor(actor, gPlayState, 0.0f), actor->world.pos.z, 0, 0, 0, param, false); +} + +bool spawningPresents = false; + +struct Present {}; + +std::unordered_map presents; + +void Present_Init(Actor* actor, PlayState* play) { + Present present; + presents[actor] = present; + + actor->gravity = -1; + Actor_MoveXZGravity(actor); + actor->shape.rot.y = Random(0, 0xFFFF); + + Actor_UpdateBgCheckInfo(play, actor, 10.0f, 10.0f, 0.0f, 0xFF); +} + +void Present_Update(Actor* actor, PlayState* play) { + Present* present = &presents[actor]; + + if (actor->xzDistToPlayer < 50.0f && actor->yDistToPlayer < 50.0f) { + uint32_t giftsCollected = CVarGetInteger(CVAR("GiftsCollected"), 0); + giftsCollected++; + CVarSetInteger(CVAR("GiftsCollected"), giftsCollected); + Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); + std::string msg = std::to_string(giftsCollected).c_str(); + msg += " Gifts in Inventory."; + Notification::Emit({ + .itemIcon = "RG_TRIFORCE_PIECE", + .message = msg, + .messageColor = ImVec4(1.0f, 1.0f, 1.0f, 1.0f), + }); + Actor_Kill(actor); + } +} + +void Present_Draw(Actor* actor, PlayState* play) { + OPEN_DISPS(play->state.gfxCtx); + + Gfx_SetupDL_25Opa(play->state.gfxCtx); + + Matrix_Scale(30.0f, 30.0f, 30.0f, MTXMODE_APPLY); + Matrix_Translate(49.20f, 0.0f, -106.60f, MTXMODE_APPLY); + gSPMatrix(POLY_OPA_DISP++, Matrix_NewMtx(play->state.gfxCtx, (char*)__FILE__, __LINE__), + G_MTX_MODELVIEW | G_MTX_LOAD); + gDPSetPrimColor(POLY_OPA_DISP++, 0, 0, 255, 255, 255, 255); + gSPDisplayList(POLY_OPA_DISP++, (Gfx*)gXmasDecor100DL); + + CLOSE_DISPS(play->state.gfxCtx); +} + +void Present_Destroy(Actor* actor, PlayState* play) { + presents.erase(actor); +} + +static void OnPresentChange() { + isExchangeDisabled = !CVarGetInteger(CVAR("OrnExch.Enabled"), 0); + COND_ID_HOOK( + OnOpenText, 0x204A, CVarGetInteger(CVAR("OrnExch.Enabled"), 0), [](u16* textId, bool* loadFromMessageTable) { + auto messageEntry = CustomMessage(""); + bool reduceGifts = false; + uint32_t giftsCollected = CVarGetInteger(CVAR("GiftsCollected"), 0); + uint32_t giftsRequired = CVarGetInteger(CVAR("OrnExch.Amount"), 15); + if (giftsCollected < giftsRequired) { + std::string msg = "You only have %r " + std::to_string(giftsCollected) + "%w If you bring me %g" + + std::to_string(giftsRequired) + "%w I'll give you a reward!"; + messageEntry = CustomMessage(msg); + } else { + std::string msg = "A present? And %g" + std::to_string(giftsRequired) + + "%w to boot? Here's your reward, bring me more if you find any!"; + messageEntry = CustomMessage(msg); + reduceGifts = true; + } + messageEntry.AutoFormat(); + messageEntry.LoadIntoFont(); + *loadFromMessageTable = false; + + if (reduceGifts) { + vanillaQueuedItemEntry = Rando::StaticData::RetrieveItem(RG_TRIFORCE_PIECE).GetGIEntry_Copy(); + giftsCollected -= giftsRequired; + CVarSetInteger(CVAR("GiftsCollected"), giftsCollected); + Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); + + std::string msg = std::to_string(giftsCollected).c_str(); + msg += " Gifts in Inventory."; + Notification::Emit({ .itemIcon = "RG_TRIFORCE_PIECE", .message = msg }); + } + }); + + COND_HOOK(OnSceneSpawnActors, CVarGetInteger(CVAR("OrnExch.Enabled"), 0), []() { + presents.clear(); + Vec3f pos; + static CollisionPoly presentPoly; + static f32 raycastResult; + pos.y = 9999.0f; + int spawnAttempts = 0; + while (spawnAttempts < 20) { + if (GET_PLAYER(gPlayState) != nullptr) { + pos.x = GET_PLAYER(gPlayState)->actor.world.pos.x; + pos.z = GET_PLAYER(gPlayState)->actor.world.pos.z; + } else { + pos.x = 0; + pos.z = 0; + } + // X/Z anywhere from -1000.0 to +1000.0 from player + pos.x += (float)(Random(0, 20000)) - 10000.0f; + pos.z += (float)(Random(0, 20000)) - 10000.0f; + + raycastResult = BgCheck_AnyRaycastFloor1(&gPlayState->colCtx, &presentPoly, &pos); + + if (raycastResult > BGCHECK_Y_MIN) { + spawningPresents = true; + Actor* actor = Actor_Spawn(&gPlayState->actorCtx, gPlayState, ACTOR_EN_OE2, pos.x, raycastResult, pos.z, + 0, 0, 0, 0, false); + spawningPresents = false; + // break; + } + + spawnAttempts++; + } + }); + + COND_ID_HOOK(ShouldActorInit, ACTOR_EN_OE2, CVarGetInteger(CVAR("OrnExch.Enabled"), 0), + [](void* actorRef, bool* should) { + Actor* actor = (Actor*)actorRef; + if (spawningPresents) { + actor->init = Present_Init; + actor->update = Present_Update; + actor->draw = Present_Draw; + actor->destroy = Present_Destroy; + } + }); +} + +static bool isRandomizingBoulder = false; +static void OnBlitzChange() { + COND_HOOK(ShouldActorInit, CVarGetInteger(CVAR("Blitz.Enabled"), 0), [](void* actorRef, bool* should) { + if (isRandomizingBoulder) + return; + + Actor* actor = (Actor*)actorRef; + for (auto& boulderActor : boulderList) { + if (actor->id == boulderActor) { + isRandomizingBoulder = true; + RandomizeBoulder(actor); + isRandomizingBoulder = false; + *should = false; + return; + } + } + }); +} + +static void OnFeverConfigurationChanged() { + isFeverDisabled = !CVarGetInteger(CVAR("Fever.Enabled"), 0); + fontScale = CVarGetFloat(CVAR("FontScale"), 1.0f); + if (fontScale < 1.0f) { + fontScale = 1.0f; + } +} + +void CaladiusWindow::Draw() { + if (!CVarGetInteger(CVAR("Fever.Enabled"), 0)) { + return; + } + + ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(0, 0, 0, 0.5f)); + ImGui::PushStyleColor(ImGuiCol_Border, ImVec4(0, 0, 0, 0)); + ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 4.0f); + ImGui::Begin("TimerDisplay", nullptr, + ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoNav | ImGuiWindowFlags_NoFocusOnAppearing | + ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoDocking | ImGuiWindowFlags_NoTitleBar | + ImGuiWindowFlags_NoScrollWithMouse | ImGuiWindowFlags_NoScrollbar); + ImGui::SetWindowFontScale(fontScale); + ImGui::Text(formatTimestampIceTrapFever(calculateRemainingTime()).c_str()); + ImGui::End(); + + ImGui::PopStyleColor(2); + ImGui::PopStyleVar(1); +} + +static void RegisterMenu() { + WidgetPath path = { "Holiday", "Gameplay", SECTION_COLUMN_2 }; + SohGui::mSohMenu->AddWidget(path, "Holiday Fever", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR("Fever.Enabled")) + .Callback([](WidgetInfo& info) { OnFeverConfigurationChanged(); }) + .Options(UIWidgets::CheckboxOptions().Tooltip( + "Death will come for you when the timer runs out? Obtaining Ice Traps extends your timer. \n\nShould be " + "enabled before starting a new file, won't work well with existing files.")); + SohGui::mSohMenu->AddWidget(path, "Starting Timer: %d minutes", WIDGET_CVAR_SLIDER_INT) + .CVar(CVAR("StartTimer")) + .Callback([](WidgetInfo& info) { OnFeverConfigurationChanged(); }) + .PreFunc([](WidgetInfo& info) { info.options.get()->disabled = !CVarGetInteger(CVAR("Fever.Enabled"), 0); }) + .Options(UIWidgets::IntSliderOptions().DefaultValue(15).Min(5).Max(30)); + SohGui::mSohMenu->AddWidget(path, "Time Extensions: %d minutes", WIDGET_CVAR_SLIDER_INT) + .CVar(CVAR("ExtendTimer")) + .Callback([](WidgetInfo& info) { OnFeverConfigurationChanged(); }) + .PreFunc([](WidgetInfo& info) { info.options.get()->disabled = !CVarGetInteger(CVAR("Fever.Enabled"), 0); }) + .Options(UIWidgets::IntSliderOptions().DefaultValue(5).Min(1).Max(10)); + + path.column = SECTION_COLUMN_1; + + SohGui::mSohMenu->AddWidget(path, "Shuffle Boulders & Ice", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR("Blitz.Enabled")) + .Callback([](WidgetInfo& info) { OnBlitzChange(); }) + .Options(UIWidgets::CheckboxOptions().Tooltip( + "Boulders & Ice will randomly be replaced with other boulders & ice when the scene loads.")); +} + +static void RegisterMod() { + OnFeverConfigurationChanged(); + OnBlitzChange(); + OnPresentChange(); +} + +static RegisterShipInitFunc initFunc(RegisterMod); +static RegisterMenuInitFunc menuInitFunc(RegisterMenu); diff --git a/soh/soh/Enhancements/Holiday/Caladius.h b/soh/soh/Enhancements/Holiday/Caladius.h new file mode 100644 index 00000000000..ff4d8c1f743 --- /dev/null +++ b/soh/soh/Enhancements/Holiday/Caladius.h @@ -0,0 +1,11 @@ +#include + +class CaladiusWindow : public Ship::GuiWindow { + public: + using GuiWindow::GuiWindow; + + void InitElement() override{}; + void DrawElement() override{}; + void Draw() override; + void UpdateElement() override{}; +}; \ No newline at end of file diff --git a/soh/soh/Enhancements/Holiday/Example.cpp b/soh/soh/Enhancements/Holiday/Example.cpp new file mode 100644 index 00000000000..5cb757559a4 --- /dev/null +++ b/soh/soh/Enhancements/Holiday/Example.cpp @@ -0,0 +1,45 @@ +#include "Holiday.hpp" + +extern "C" { +#include "macros.h" +#include "functions.h" +#include "variables.h" +extern PlayState* gPlayState; + +// TODO: Include anything you need here from C land +} + +// TODO: Change this to YourName +#define AUTHOR "Example" +#define CVAR(v) "gHoliday." AUTHOR "." v + +static void OnConfigurationChanged() { + // TODO: Register any hooks or things that need to run on startup and when the main CVar is toggled + // Note: Hooks should be registered/unregistered depending on the CVar state (Use COND_HOOK or COND_ID_HOOK) + + // COND_HOOK(OnSceneSpawnActors, CVarGetInteger(CVAR("Enabled"), 0), []() { + // // Spawn your own actors? + // }); + + // COND_ID_HOOK(OnActorInit, ACTOR_OBJ_TSUBO, CVarGetInteger(CVAR("DoSomethingWithPots"), 0), [](void* actorRef) { + // // Do something with pots? + // }); +} + +static void DrawMenu() { + // ImGui::SeparatorText(AUTHOR); + // if (UIWidgets::EnhancementCheckbox("DoSomethingWithPots", CVAR("DoSomethingWithPots"))) { + // OnConfigurationChanged(); + // } +} + +static void RegisterMod() { + // #region Leave this alone unless you know what you are doing + OnConfigurationChanged(); + // #endregion + + // TODO: Anything you want to run once on startup +} + +// TODO: Uncomment this line to enable the mod +// static Holiday holiday(DrawMenu, RegisterMod); diff --git a/soh/soh/Enhancements/Holiday/Fredomato.cpp b/soh/soh/Enhancements/Holiday/Fredomato.cpp new file mode 100644 index 00000000000..29fb92bea06 --- /dev/null +++ b/soh/soh/Enhancements/Holiday/Fredomato.cpp @@ -0,0 +1,465 @@ +#include "Holiday.hpp" +#include +#include "soh/SohGui/UIWidgets.hpp" +#include "soh/Enhancements/game-interactor/GameInteractor.h" +#include "objects/object_dog/object_dog.h" +#include "soh/frame_interpolation.h" +#include "soh/Enhancements/randomizer/3drando/random.hpp" +#include "soh/Enhancements/randomizer/location_access.h" +#include "soh/Enhancements/randomizer/entrance.h" +#include "soh/Notification/Notification.h" +#include "soh/Enhancements/custom-item/CustomItem.h" +#include "soh/Enhancements/nametag.h" + +#include "objects/gameplay_field_keep/gameplay_field_keep.h" +#include "objects/gameplay_keep/gameplay_keep.h" +#include "objects/object_md/object_md.h" +#include "objects/object_trap/object_trap.h" +#include "objects/object_toryo/object_toryo.h" +#include "src/overlays/actors/ovl_Door_Ana/z_door_ana.h" +extern "C" { +#include "macros.h" +#include "functions.h" +#include "variables.h" + +extern PlayState* gPlayState; +void DoorAna_SetupAction(DoorAna* doorAna, DoorAnaActionFunc actionFunc); +void DoorAna_GrabPlayer(DoorAna* doorAna, PlayState* play); +} +extern GetItemEntry vanillaQueuedItemEntry; + +#define CVAR(v) "gHoliday.Gameplay." v + +static CollisionPoly snowballPoly; +static f32 raycastResult; + +const s16 entrances[] = { + 0x0000, 0x0209, 0x0004, 0x0242, 0x0028, 0x0221, 0x0169, 0x0215, 0x0165, 0x024A, 0x0010, 0x021D, 0x0082, 0x01E1, + 0x0037, 0x0205, 0x0098, 0x02A6, 0x0088, 0x03D4, 0x0008, 0x03A8, 0x0467, 0x023D, 0x0433, 0x0443, 0x0437, 0x0447, + 0x009C, 0x033C, 0x00C9, 0x026A, 0x00C1, 0x0266, 0x0043, 0x03CC, 0x045F, 0x0309, 0x03A0, 0x03D0, 0x007E, 0x026E, + 0x0530, 0x01D1, 0x0507, 0x03BC, 0x0388, 0x02A2, 0x0063, 0x01D5, 0x0528, 0x03C0, 0x043B, 0x0067, 0x02FD, 0x0349, + 0x0550, 0x04EE, 0x039C, 0x0345, 0x05C8, 0x05DC, 0x0072, 0x034D, 0x030D, 0x0355, 0x037C, 0x03FC, 0x0380, 0x03C4, + 0x004F, 0x0378, 0x02F9, 0x042F, 0x05D0, 0x05D4, 0x052C, 0x03B8, 0x016D, 0x01CD, 0x00B7, 0x0201, 0x003B, 0x0463, + 0x0588, 0x057C, 0x0578, 0x0340, 0x04C2, 0x03E8, 0x04BE, 0x0482, 0x0315, 0x045B, 0x0371, 0x0394, 0x0272, 0x0211, + 0x0053, 0x0472, 0x0453, 0x0351, 0x0384, 0x044B, 0x03EC, 0x04FF, 0x0700, 0x0800, 0x0701, 0x0801, 0x0702, 0x0802, + 0x0703, 0x0803, 0x0704, 0x0804, 0x0705, 0x0805, 0x0706, 0x0806, 0x0707, 0x0807, 0x0708, 0x0808, 0x0709, 0x0809, + 0x070A, 0x080A, 0x070B, 0x080B, 0x070C, 0x080C, 0x070D, 0x080D, 0x070E, 0x080E, 0x070F, 0x080F, 0x0710, 0x0711, + 0x0811, 0x0712, 0x0812, 0x0713, 0x0813, 0x0714, 0x0814, 0x0715, 0x0815, 0x0716, 0x0816, 0x0717, 0x0817, 0x0718, + 0x0818, 0x0719, 0x0819, 0x081A, 0x071B, 0x081B, 0x071C, 0x081C, 0x071D, 0x081D, 0x071E, 0x081E, 0x071F, 0x081F, + 0x0720, 0x0820, 0x004B, 0x035D, 0x031C, 0x0361, 0x002D, 0x050B, 0x044F, 0x0359, 0x05E0, 0x020D, 0x011E, 0x0286, + 0x04E2, 0x04D6, 0x01DD, 0x04DA, 0x00FC, 0x01A9, 0x0185, 0x04DE, 0x0102, 0x0189, 0x0117, 0x018D, 0x0276, 0x01FD, + 0x00DB, 0x017D, 0x00EA, 0x0181, 0x0157, 0x01F9, 0x0328, 0x0560, 0x0129, 0x022D, 0x0130, 0x03AC, 0x0123, 0x0365, + 0x00B1, 0x0033, 0x0138, 0x025A, 0x0171, 0x025E, 0x00E4, 0x0195, 0x013D, 0x0191, 0x014D, 0x01B9, 0x0246, 0x01C1, + 0x0147, 0x01BD, 0x0108, 0x019D, 0x0225, 0x01A1, 0x0219, 0x027E, 0x0554, 0x00BB, 0x0282, 0x0600, 0x04F6, 0x0604, + 0x01F1, 0x0568, 0x05F4, 0x040F, 0x0252, 0x040B, 0x00C5, 0x0301, 0x0407, 0x000C, 0x024E, 0x0305, 0x0175, 0x0417, + 0x0423, 0x008D, 0x02F5, 0x0413, 0x02B2, 0x0457, 0x047A, 0x010E, 0x0608, 0x0564, 0x060C, 0x0610, 0x0580 +}; + +static bool midoGrottoInit = false; +static SkelAnime midoSkelAnime; +static Vec3s midoJointTable[17]; +static Vec3s midoMorphTable[17]; +int FredsQuestWoodCollected = 0; +int FredsQuestWoodOnHand = 0; +static int lastDisplayedCount = -1; +static bool FredsQuestComplete = false; +static SkelAnime collectionPointSkelAnime; +static Vec3s collectionPointJointTable[17]; +static Vec3s collectionPointMorphTable[17]; +static std::string collectionPointNametag; + +static void RandomGrotto_WaitOpen(DoorAna* doorAna, PlayState* play) { + if (!midoGrottoInit) { + midoGrottoInit = true; + SkelAnime_InitFlex(play, &midoSkelAnime, (FlexSkeletonHeader*)&gMidoSkel, (AnimationHeader*)&gMidoWalkingAnim, + midoJointTable, midoMorphTable, 17); + } + SkelAnime_Update(&midoSkelAnime); + + Actor* actor = &doorAna->actor; + Player* player = GET_PLAYER(play); + if (!Player_InCsMode(play)) { + Math_SmoothStepToF(&actor->world.pos.x, player->actor.world.pos.x, 0.1f, 10.0f, 0.0f); + Math_SmoothStepToF(&actor->world.pos.z, player->actor.world.pos.z, 0.1f, 10.0f, 0.0f); + Math_SmoothStepToF(&actor->world.pos.y, player->actor.world.pos.y, 0.1f, 10.0f, 0.0f); + } + + Math_ApproachS(&doorAna->actor.shape.rot.y, doorAna->actor.yawTowardsPlayer, 5, 0xBB8); + + if (Math_StepToF(&actor->scale.x, 0.01f, 0.001f)) { + if ((actor->targetMode != 0) && (play->transitionTrigger == TRANS_TRIGGER_OFF) && + (player->stateFlags1 & PLAYER_STATE1_FLOOR_DISABLED) && (player->av1.actionVar1 == 0)) { + play->nextEntranceIndex = RandomElement(entrances); + DoorAna_SetupAction((DoorAna*)actor, DoorAna_GrabPlayer); + } else { + if (!Player_InCsMode(play) && !(player->stateFlags1 & (PLAYER_STATE1_ON_HORSE | PLAYER_STATE1_IN_WATER)) && + actor->xzDistToPlayer <= 15.0f && -50.0f <= actor->yDistToPlayer && actor->yDistToPlayer <= 15.0f) { + player->stateFlags1 |= PLAYER_STATE1_FLOOR_DISABLED; + actor->targetMode = 1; + } else { + actor->targetMode = 0; + } + } + } + Actor_SetScale(actor, actor->scale.x); +} + +static void RandomGrotto_Draw(Actor* actor, PlayState* play) { + if (!midoGrottoInit) { + return; + } + OPEN_DISPS(play->state.gfxCtx); + + Gfx_SetupDL_25Xlu(play->state.gfxCtx); + gSPMatrix(POLY_XLU_DISP++, Matrix_NewMtx(play->state.gfxCtx, (char*)__FILE__, __LINE__), + G_MTX_MODELVIEW | G_MTX_LOAD); + gSPDisplayList(POLY_XLU_DISP++, (Gfx*)gGrottoDL); + + Matrix_Translate(0.0f, -2700.0f, 0.0f, MTXMODE_APPLY); + gSPMatrix(POLY_OPA_DISP++, Matrix_NewMtx(play->state.gfxCtx, (char*)__FILE__, __LINE__), + G_MTX_MODELVIEW | G_MTX_LOAD); + gSPSegment(POLY_OPA_DISP++, 0x08, (uintptr_t)gMidoEyeOpenTex); + func_80034BA0(play, &midoSkelAnime, NULL, NULL, actor, 255); + + CLOSE_DISPS(play->state.gfxCtx); +} + +static Vec3f FindValidPos(f32 distance) { + Vec3f pos; + pos.y = 9999.0f; + int attempts = 0; + + while (attempts++ < 20) { + if (GET_PLAYER(gPlayState) != nullptr) { + pos.x = GET_PLAYER(gPlayState)->actor.world.pos.x; + pos.z = GET_PLAYER(gPlayState)->actor.world.pos.z; + } else { + pos.x = 0; + pos.z = 0; + } + pos.x += (float)(Random(0, distance)) - distance / 2; + pos.z += (float)(Random(0, distance)) - distance / 2; + + raycastResult = BgCheck_AnyRaycastFloor1(&gPlayState->colCtx, &snowballPoly, &pos); + + if (raycastResult > BGCHECK_Y_MIN) { + pos.y = raycastResult; + return pos; + } + } + + return pos; +} + +// TODO: If in hyrule field and treeChopper is on, teleport somewhere else in hyrule field +static void SpawnRandomGrotto() { + if (gPlayState->sceneNum == SCENE_TEMPLE_OF_TIME_EXTERIOR_DAY || + gPlayState->sceneNum == SCENE_TEMPLE_OF_TIME_EXTERIOR_NIGHT || + gPlayState->sceneNum == SCENE_TEMPLE_OF_TIME_EXTERIOR_RUINS) { + return; + } + + Vec3f pos = FindValidPos(2000.0f); + if (pos.y == 9999.0f) { + return; + } + + Actor* grotto = + Actor_Spawn(&gPlayState->actorCtx, gPlayState, ACTOR_DOOR_ANA, pos.x, pos.y, pos.z, 0, 0, 0, 0, false); + midoGrottoInit = false; + DoorAna_SetupAction((DoorAna*)grotto, RandomGrotto_WaitOpen); + grotto->draw = RandomGrotto_Draw; +} + +void SpawnStick(Vec3f pos) { +} + +Actor* specialTree = nullptr; + +void ChooseSpecialTree() { + Actor* actor = gPlayState->actorCtx.actorLists[ACTORCAT_PROP].head; + std::vector trees; + + specialTree = nullptr; + + while (actor != NULL) { + if (ACTOR_EN_WOOD02 == actor->id && actor->params < 10) { + trees.push_back(actor); + } + actor = actor->next; + } + + if (trees.size() <= 1) { + return; + } + + specialTree = trees[Random(0, trees.size() - 1)]; +} + +extern "C" bool HandleTreeBonk(Actor* actor) { + if (!CVarGetInteger(CVAR("FredsQuest.Enabled"), 0)) { + return false; + } + + int damage = 2; + // random chance of doing a crit + if (Random(0, 100) < 30) { + damage = 4; + } + + if (actor->colChkInfo.health - damage <= 0) { + if (specialTree == actor) { + ChooseSpecialTree(); + + for (int i = 0; i < CVarGetInteger(CVAR("FredsQuest.SpecialBreakDropRate"), 10); i++) { + SpawnStick(actor->world.pos); + } + } else { + for (int i = 0; i < CVarGetInteger(CVAR("FredsQuest.TreeBreakDropRate"), 3); i++) { + SpawnStick(actor->world.pos); + } + } + + // Move tree (instead of killing and spawning another) + actor->colChkInfo.health = 8; + Vec3f pos = FindValidPos(5000.0f); + actor->world.pos.x = pos.x; + actor->world.pos.y = pos.y; + actor->world.pos.z = pos.z; + } else { + actor->colChkInfo.health -= damage; + for (int i = 0; i < CVarGetInteger(CVAR("FredsQuest.TreeBonkDropRate"), 1); i++) { + SpawnStick(actor->world.pos); + } + } + + return true; +} + +void DrawCrazyTaxiArrow(Actor* actor, PlayState* play) { + if (specialTree == nullptr || !CVarGetInteger(CVAR("FredsQuest.CrazyTaxiArrow"), 0)) { + return; + } + + s16 yaw = Actor_WorldYawTowardActor(actor, specialTree); + Math_ApproachS(&actor->shape.rot.y, yaw, 5, 10000); + + OPEN_DISPS(gPlayState->state.gfxCtx); + + Gfx_SetupDL_4Xlu(gPlayState->state.gfxCtx); + + Matrix_Scale(50.0f, 50.0f, 50.0f, MTXMODE_APPLY); + Matrix_Translate(0.0f, 70.0f, 0.0f, MTXMODE_APPLY); + Matrix_RotateY(5.86f, MTXMODE_APPLY); + + gSPMatrix(POLY_XLU_DISP++, Matrix_NewMtx(play->state.gfxCtx, (char*)__FILE__, __LINE__), + G_MTX_MODELVIEW | G_MTX_LOAD); + gDPSetPrimColor(POLY_XLU_DISP++, 0, 0, 0, 255, 0, 255); + gDPSetEnvColor(POLY_XLU_DISP++, 0, 255, 0, 255); + gSPDisplayList(POLY_XLU_DISP++, (Gfx*)gDebugArrowDL); + + CLOSE_DISPS(gPlayState->state.gfxCtx); +} + +void SpawnCrazyTaxiArrow() { +} + +void CollectionPoint_Update(Actor* actor, PlayState* play) { + EnItem00* enItem00 = (EnItem00*)actor; + + SkelAnime_Update(&collectionPointSkelAnime); + + if (FredsQuestComplete) { + return; + } + + if (lastDisplayedCount != FredsQuestWoodCollected) { + lastDisplayedCount = FredsQuestWoodCollected; + collectionPointNametag = "Bring me wood!"; + if (FredsQuestWoodCollected > 0) { + collectionPointNametag += std::string(" (") + std::to_string(FredsQuestWoodCollected) + "/" + + std::to_string(CVarGetInteger(CVAR("FredsQuest.WoodNeeded"), 300)) + ")"; + } + NameTag_RemoveAllForActor(actor); + NameTag_RegisterForActorWithOptions(actor, collectionPointNametag.c_str(), { .yOffset = 100 }); + } + + if ((actor->xzDistToPlayer <= 200.0f) && (fabsf(actor->yDistToPlayer) <= fabsf(50.0f))) { + if (FredsQuestWoodOnHand) { + FredsQuestWoodCollected++; + FredsQuestWoodOnHand--; + Audio_PlaySoundGeneral(NA_SE_SY_METRONOME, &gSfxDefaultPos, 4, &gSfxDefaultFreqAndVolScale, + &gSfxDefaultFreqAndVolScale, &gSfxDefaultReverb); + + if (FredsQuestWoodCollected >= CVarGetInteger(CVAR("FredsQuest.WoodNeeded"), 300)) { + FredsQuestComplete = true; + collectionPointNametag = "You're a hero!"; + NameTag_RemoveAllForActor(actor); + NameTag_RegisterForActorWithOptions(actor, collectionPointNametag.c_str(), { .yOffset = 100 }); + + if (IS_RANDO && Rando::Context::GetInstance()->GetOption(RSK_TRIFORCE_HUNT)) { + vanillaQueuedItemEntry = Rando::StaticData::RetrieveItem(RG_TRIFORCE_PIECE).GetGIEntry_Copy(); + } else { + vanillaQueuedItemEntry = Rando::StaticData::RetrieveItem(RG_HEART_CONTAINER).GetGIEntry_Copy(); + } + } + } + } +} + +void CollectionPoint_Draw(Actor* actor, PlayState* play) { + OPEN_DISPS(play->state.gfxCtx); + + Gfx_SetupDL_25Opa(play->state.gfxCtx); + SkelAnime_DrawSkeletonOpa(play, &collectionPointSkelAnime, NULL, NULL, actor); + + // For every 2% of the goal, draw a stick at a different angle, building a tree + Matrix_Scale(40.0f, 40.0f, 40.0f, MTXMODE_APPLY); + Matrix_Translate(0, 0, -300.0f, MTXMODE_APPLY); + for (int i = 0; i < FredsQuestWoodCollected / (CVarGetInteger(CVAR("FredsQuest.WoodNeeded"), 300) / 50); i++) { + float angle = 10 * i; + float radius = (50 - i) * 0.5f; // Radius decreases as it goes up + float height = 10.0f; // Incremental height + + Matrix_Translate(radius * cosf(angle), height, radius * sinf(angle), MTXMODE_APPLY); + Matrix_RotateY(angle, MTXMODE_APPLY); + GetItem_Draw(play, GID_STICK); + } + + CLOSE_DISPS(play->state.gfxCtx); +} + +void SpawnCollectionPoint() { +} + +void RandomTrap_Update(Actor* actor, PlayState* play) { + EnItem00* enItem00 = (EnItem00*)actor; + + enItem00->unk_158--; + if (enItem00->unk_158 == 0) { + Actor_Kill(actor); + return; + } + + Math_ApproachS(&actor->world.rot.y, actor->yawTowardsPlayer, 5, 0xBB8); + actor->speedXZ = 3.0f; + + // TODO: CVar for speed + // Multiply speed by distance + actor->speedXZ += actor->xzDistToPlayer * 0.01f; + if (actor->xzDistToPlayer > 1000.0f && actor->velocity.y < -3.0f) { + actor->velocity.y += ABS(actor->yDistToPlayer) * 0.01f; + } + + actor->shape.rot.y += 0x1000; + + if ((actor->xzDistToPlayer <= 50.0f) && (fabsf(actor->yDistToPlayer) <= fabsf(20.0f))) { + // TODO: Random crowd control effect + GameInteractor::RawAction::KnockbackPlayer(5.0f); + Actor_Kill(actor); + } + + if (actor->gravity != 0.0f) { + Actor_MoveXZGravity(actor); + Actor_UpdateBgCheckInfo(play, actor, 20.0f, 15.0f, 15.0f, 0x1D); + } + + if (actor->bgCheckFlags & 0x0003) { + actor->speedXZ = 0.0f; + } +} + +void RandomTrap_Draw(Actor* actor, PlayState* play) { + OPEN_DISPS(play->state.gfxCtx); + + Matrix_Scale(4.0f, 4.0f, 4.0f, MTXMODE_APPLY); + Matrix_Translate(0, -200.0f, 0, MTXMODE_APPLY); + func_8002EBCC(actor, play, 1); + Gfx_DrawDListOpa(play, (Gfx*)gSlidingBladeTrapDL); + + CLOSE_DISPS(play->state.gfxCtx); +} + +void SpawnRandomTrap() { + Vec3f pos = FindValidPos(2000.0f); + if (pos.y == 9999.0f) { + return; + } + + EnItem00* randomTrap = CustomItem::Spawn(pos.x, pos.y, pos.z, 0, CustomItem::TOSS_ON_SPAWN, 0, NULL, NULL); + SoundSource_PlaySfxAtFixedWorldPos(gPlayState, &randomTrap->actor.world.pos, 20, NA_SE_EV_LIGHTNING); + randomTrap->actor.update = RandomTrap_Update; + randomTrap->actor.draw = RandomTrap_Draw; + randomTrap->unk_158 = 20 * CVarGetInteger(CVAR("RandomTraps.Lifetime"), 30); +} + +void OnSceneInit() { + // Reset wood collected + FredsQuestWoodCollected = 0; + FredsQuestWoodOnHand = 0; + lastDisplayedCount = -1; + FredsQuestComplete = false; + + if (gPlayState->sceneNum != SCENE_HYRULE_FIELD) { + return; + } + + ChooseSpecialTree(); + SpawnCrazyTaxiArrow(); + SpawnCollectionPoint(); +} + +static void OnConfigurationChanged() { + COND_HOOK(OnSceneSpawnActors, CVarGetInteger(CVAR("FredsQuest.Enabled"), 0), OnSceneInit); + + COND_HOOK(OnPlayerUpdate, CVarGetInteger(CVAR("RandomTraps.Enabled"), 0), []() { + if (rand() % CVarGetInteger(CVAR("RandomTraps.SpawnChance"), 400) == 0) { + SpawnRandomTrap(); + } + }); + + COND_HOOK(OnPlayerUpdate, CVarGetInteger(CVAR("FredsQuest.Enabled"), 0), []() { + if (CVarGetInteger(CVAR("FredsQuest.EncumberedThreshold"), 60) == 0 || + FredsQuestWoodOnHand <= CVarGetInteger(CVAR("FredsQuest.EncumberedThreshold"), 60)) { + GameInteractor::State::MovementSpeedMultiplier = 0; + } else { + GameInteractor::State::MovementSpeedMultiplier = -2; + } + }); + + COND_VB_SHOULD(VB_PLAYER_ROLL, CVarGetInteger(CVAR("FredsQuest.Enabled"), 0), { + if (FredsQuestWoodOnHand > CVarGetInteger(CVAR("FredsQuest.EncumberedThreshold"), 0)) { + *should = false; + } + }); +} + +static void RegisterMenu() { + WidgetPath path = { "Holiday", "Gameplay", SECTION_COLUMN_2 }; + + SohGui::mSohMenu->AddWidget(path, "Chasing Knockback Spikes", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR("RandomTraps.Enabled")) + .Options(UIWidgets::CheckboxOptions().Tooltip( + "Random spikes will spawn around you at a configurable rate, chasing you for a short time before " + "disappearing. If they touch you, you get knocked back.")); + + SohGui::mSohMenu->AddWidget(path, "Trap Lifetime (Seconds)", WIDGET_CVAR_SLIDER_INT) + .CVar(CVAR("RandomTraps.Lifetime")) + .Callback([](WidgetInfo& info) { OnConfigurationChanged(); }) + .PreFunc( + [](WidgetInfo& info) { info.options.get()->disabled = !CVarGetInteger(CVAR("RandomTraps.Enabled"), 0); }) + .Options(UIWidgets::IntSliderOptions().DefaultValue(30).Min(0).Max(60)); + + SohGui::mSohMenu->AddWidget(path, "Spawn Chance", WIDGET_CVAR_SLIDER_INT) + .CVar(CVAR("RandomTraps.SpawnChance")) + .Callback([](WidgetInfo& info) { OnConfigurationChanged(); }) + .PreFunc( + [](WidgetInfo& info) { info.options.get()->disabled = !CVarGetInteger(CVAR("RandomTraps.Enabled"), 0); }) + .Options(UIWidgets::IntSliderOptions().DefaultValue(1000).Min(40).Max(2000)); +} + +static RegisterShipInitFunc initFunc(OnConfigurationChanged, { + CVAR("FredsQuest.Enabled"), + CVAR("RandomTraps.Enabled"), + }); +static RegisterMenuInitFunc menuInitFunc(RegisterMenu); diff --git a/soh/soh/Enhancements/Holiday/Fredomato.h b/soh/soh/Enhancements/Holiday/Fredomato.h new file mode 100644 index 00000000000..3479b3f1ecc --- /dev/null +++ b/soh/soh/Enhancements/Holiday/Fredomato.h @@ -0,0 +1,18 @@ +#ifndef FRED_H +#define FRED_H + +#ifdef __cplusplus +extern int FredsQuestWoodCollected; +extern int FredsQuestWoodOnHand; + +extern "C" { +#include "z64actor.h" +#endif + +bool HandleTreeBonk(Actor* actor); + +#ifdef __cplusplus +} +#endif + +#endif \ No newline at end of file diff --git a/soh/soh/Enhancements/Holiday/Grimey.cpp b/soh/soh/Enhancements/Holiday/Grimey.cpp new file mode 100644 index 00000000000..11d2c78737f --- /dev/null +++ b/soh/soh/Enhancements/Holiday/Grimey.cpp @@ -0,0 +1,208 @@ +#include "Holiday.hpp" +#include "soh/Enhancements/randomizer/3drando/random.hpp" +#include "soh/frame_interpolation.h" +#include "soh_assets.h" +#include "overlays/actors/ovl_En_Nutsball/z_en_nutsball.h" + +extern "C" { +#include "macros.h" +#include "functions.h" +#include "variables.h" + +#include "objects/gameplay_field_keep/gameplay_field_keep.h" +extern PlayState* gPlayState; +void func_80ABBBA8(EnNutsball* nut, PlayState* play); +void EnNutsball_Draw(Actor* nut, PlayState* play); +} + +#define CVAR(v) "gHoliday.Gameplay." v + +static bool spawningPenguins = false; +static u32 hailstormActiveTimer = 0; + +typedef enum { + PENGUIN_STATE_IDLE, + PENGUIN_STATE_WALK, +} PenguinState; + +struct Penguin { + PenguinState state; + s16 timer; + s16 targetRot; +}; + +std::unordered_map penguins; + +void Penguin_Init(Actor* actor, PlayState* play) { + Penguin penguin; + penguin.state = PENGUIN_STATE_IDLE; + penguin.timer = 0; + actor->world.rot.y = penguin.targetRot = rand() % 0x10000; + penguins[actor] = penguin; + actor->gravity = -1.0f; + actor->flags &= ~ACTOR_FLAG_ATTENTION_ENABLED; +} + +void Penguin_Update(Actor* actor, PlayState* play) { + Penguin* penguin = &penguins[actor]; + + if (penguin->timer <= 0) { + if (penguin->state == PENGUIN_STATE_IDLE) { + penguin->state = (PenguinState)(rand() % 3); + penguin->timer = rand() % (20 * 10) + (20 * 3); + } else { + penguin->state = PENGUIN_STATE_IDLE; + penguin->timer = rand() % (20 * 10) + (20 * 3); + } + } else { + penguin->timer--; + } + + if (rand() % 100 == 0) { + penguin->targetRot = rand() % 0x10000; + } + + switch (penguin->state) { + case PENGUIN_STATE_IDLE: + break; + case PENGUIN_STATE_WALK: + actor->speedXZ = 0.5f; + break; + } + + Math_SmoothStepToS(&actor->world.rot.y, penguin->targetRot, 1, 200, 0); + actor->shape.rot.y = actor->world.rot.y; + + if (actor->speedXZ < 0.0f) { + actor->speedXZ = 0.0f; + } + + Actor_MoveXZGravity(actor); + + Actor_UpdateBgCheckInfo(play, actor, 10.0f, 10.0f, 0.0f, 0xFF); +} + +void Penguin_Draw(Actor* actor, PlayState* play) { + OPEN_DISPS(play->state.gfxCtx); + + Gfx_SetupDL_25Opa(play->state.gfxCtx); + + Matrix_Scale(0.8f, 0.8f, 0.8f, MTXMODE_APPLY); + Matrix_Translate(0, 2000.0f, 0, MTXMODE_APPLY); + gSPMatrix(POLY_OPA_DISP++, Matrix_NewMtx(play->state.gfxCtx, (char*)__FILE__, __LINE__), + G_MTX_MODELVIEW | G_MTX_LOAD); + gDPSetPrimColor(POLY_OPA_DISP++, 0, 0, 255, 255, 255, 255); + gSPDisplayList(POLY_OPA_DISP++, (Gfx*)gPenguinDL); + + CLOSE_DISPS(play->state.gfxCtx); +} + +void Penguin_Destroy(Actor* actor, PlayState* play) { + penguins.erase(actor); +} + +static void OnConfigurationChanged() { + COND_HOOK(OnPlayerUpdate, CVarGetInteger(CVAR("Hailstorm"), 0), []() { + // Every frame has a 1/1000 chance to start a hailstorm if there isn't one already + if (hailstormActiveTimer == 0 && rand() % 1000 == 0) { + hailstormActiveTimer = 20 * 20; // Lasts for 20 seconds + } + if (hailstormActiveTimer > 0) { + hailstormActiveTimer--; + if (rand() % 3 == 0) { + int spawned = 0; + while (spawned < 1) { + Vec3f pos = GET_PLAYER(gPlayState)->actor.world.pos; + pos.x += (float)Random(0, 500) - 250.0f; + pos.z += (float)Random(0, 500) - 250.0f; + pos.y += 500.0f; + + Actor* actor = Actor_Spawn(&gPlayState->actorCtx, gPlayState, ACTOR_EN_NUTSBALL, pos.x, pos.y, + pos.z, 0, 0, 0, 0, false); + EnNutsball* nut = (EnNutsball*)actor; + nut->actor.draw = EnNutsball_Draw; + nut->actor.shape.rot.y = 0; + nut->timer = 0; + nut->actionFunc = func_80ABBBA8; + nut->actor.speedXZ = 0.0f; + nut->actor.gravity = -2.0f; + spawned++; + } + } + } + }); + + COND_HOOK(OnSceneSpawnActors, CVarGetInteger(CVAR("Penguins"), 0), []() { + penguins.clear(); + + if (gPlayState->sceneNum != SCENE_HYRULE_FIELD) { + return; + } + + static Vec3f huddlePos; + static Vec3f spawnPos; + static f32 raycastResult; + static CollisionPoly poly; + + spawningPenguins = true; + + int huddlesSpawned = 0; + while (huddlesSpawned < 10) { + huddlePos.x = (float)(Random((gPlayState->sceneNum == SCENE_HYRULE_FIELD ? -10000 : -2700) + 10000, + (gPlayState->sceneNum == SCENE_HYRULE_FIELD ? 5000 : 2000) + 10000) - + (float)10000.0f); + huddlePos.y = 5000; + huddlePos.z = (float)(Random((gPlayState->sceneNum == SCENE_HYRULE_FIELD ? -1000 : -2000) + 10000, + (gPlayState->sceneNum == SCENE_HYRULE_FIELD ? 15000 : 2000) + 10000) - + (float)10000.0f); + + if (BgCheck_AnyRaycastFloor1(&gPlayState->colCtx, &poly, &huddlePos) <= BGCHECK_Y_MIN) { + continue; + } + + // 5-10 + int huddleSize = rand() % 6 + 5; + int penguinsSpawned = 0; + while (penguinsSpawned < huddleSize) { + spawnPos.x = huddlePos.x + rand() % 100 - 50; + spawnPos.y = huddlePos.y; + spawnPos.z = huddlePos.z + rand() % 100 - 50; + + raycastResult = BgCheck_AnyRaycastFloor1(&gPlayState->colCtx, &poly, &spawnPos); + + if (raycastResult > BGCHECK_Y_MIN) { + Actor_Spawn(&gPlayState->actorCtx, gPlayState, ACTOR_EN_OE2, spawnPos.x, raycastResult, spawnPos.z, + 0, 0, 0, 0, false); + penguinsSpawned++; + } + } + huddlesSpawned++; + } + + spawningPenguins = false; + }); + + COND_ID_HOOK(ShouldActorInit, ACTOR_EN_OE2, CVarGetInteger(CVAR("Penguins"), 0), [](void* actorRef, bool* should) { + Actor* actor = (Actor*)actorRef; + if (spawningPenguins) { + actor->init = Penguin_Init; + actor->update = Penguin_Update; + actor->draw = Penguin_Draw; + actor->destroy = Penguin_Destroy; + } + }); +} + +static void RegisterMenu() { + WidgetPath path = { "Holiday", "Gameplay", SECTION_COLUMN_1 }; + + SohGui::mSohMenu->AddWidget(path, "Hailstorm", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR("Hailstorm")) + .Options(UIWidgets::CheckboxOptions().Tooltip("Occasional hailstorms throughout hyrule")); +} + +static RegisterShipInitFunc initFunc(OnConfigurationChanged, { + CVAR("Penguins"), + CVAR("Hailstorm"), + }); +static RegisterMenuInitFunc menuInitFunc(RegisterMenu); diff --git a/soh/soh/Enhancements/Holiday/Holiday.hpp b/soh/soh/Enhancements/Holiday/Holiday.hpp new file mode 100644 index 00000000000..19b02c1912f --- /dev/null +++ b/soh/soh/Enhancements/Holiday/Holiday.hpp @@ -0,0 +1,20 @@ +#ifndef HOLIDAY_HPP +#define HOLIDAY_HPP + +#include +#include +#include +#include "soh/Enhancements/game-interactor/GameInteractor.h" +#include "soh/Enhancements/cosmetics/CosmeticsEditor.h" +#include "soh/SohGui/SohMenu.h" +#include "soh/SohGui/SohGui.hpp" +#include "soh/SohGui/UIWidgets.hpp" + +inline std::vector> holidayDrawFuncs = {}; +inline std::vector> holidayRegisterFuncs = {}; + +namespace SohGui { +extern std::shared_ptr mSohMenu; +} + +#endif // HOLIDAY_HPP diff --git a/soh/soh/Enhancements/Holiday/LL.cpp b/soh/soh/Enhancements/Holiday/LL.cpp new file mode 100644 index 00000000000..47794799260 --- /dev/null +++ b/soh/soh/Enhancements/Holiday/LL.cpp @@ -0,0 +1,98 @@ +#include "Holiday.hpp" +#include "LL.h" + +extern "C" { +#include "macros.h" +#include "functions.h" +#include "variables.h" +extern PlayState* gPlayState; + +// TODO: Include anything you need here from C land +} + +#define CVAR(v) "gHoliday.Gameplay." v + +static ImVec4 customColorZero = RAINBOW_PRESETS[0][0]; +static ImVec4 customColorOne = RAINBOW_PRESETS[0][1]; +static ImVec4 customColorMinusZero = RAINBOW_PRESETS[0][2]; +static ImVec4 customColorMinusOne = RAINBOW_PRESETS[0][3]; + +ImVec4 Color_LUSToImGui(Color_RGBA8 color) { + ImVec4 result; + + result.x = color.r / 255.0f; + result.y = color.g / 255.0f; + result.z = color.b / 255.0f; + result.w = color.a / 255.0f; + + return result; +} + +Color_RGBA8 Color_ImGuiToLUS(ImVec4 color) { + Color_RGBA8 result; + + result.r = static_cast(color.x * 255); + result.g = static_cast(color.y * 255); + result.b = static_cast(color.z * 255); + result.a = static_cast(color.w * 255); + + return result; +} + +static void OnConfigurationChanged() { + Color_RGBA8 c1 = CVarGetColor(CVAR("CustomRainbow1"), Color_ImGuiToLUS(RAINBOW_PRESETS[0][0])); + Color_RGBA8 c2 = CVarGetColor(CVAR("CustomRainbow2"), Color_ImGuiToLUS(RAINBOW_PRESETS[0][1])); + Color_RGBA8 c3 = CVarGetColor(CVAR("CustomRainbow3"), Color_ImGuiToLUS(RAINBOW_PRESETS[0][2])); + Color_RGBA8 c4 = CVarGetColor(CVAR("CustomRainbow4"), Color_ImGuiToLUS(RAINBOW_PRESETS[0][3])); + + customColorZero = Color_LUSToImGui((Color_RGBA8)c1); + customColorOne = Color_LUSToImGui((Color_RGBA8)c2); + customColorMinusZero = Color_LUSToImGui((Color_RGBA8)c3); + customColorMinusOne = Color_LUSToImGui((Color_RGBA8)c4); +} + +static void RegisterMenu() { + WidgetPath path = { "Holiday", "Gameplay", SECTION_COLUMN_1 }; + + SohGui::mSohMenu->AddWidget(path, "Custom Rainbows", WIDGET_CVAR_CHECKBOX).CVar(CVAR("EnableCustomRainbows")); + + SohGui::mSohMenu->AddWidget(path, "Color 1", WIDGET_CVAR_COLOR_PICKER) + .CVar(CVAR("CustomRainbow1")) + .Options(UIWidgets::ColorPickerOptions().ShowRandom().ShowReset().UseAlpha(false).DefaultValue( + Color_ImGuiToLUS(RAINBOW_PRESETS[0][0]))); + SohGui::mSohMenu->AddWidget(path, "Color 2", WIDGET_CVAR_COLOR_PICKER) + .CVar(CVAR("CustomRainbow2")) + .Options(UIWidgets::ColorPickerOptions().ShowRandom().ShowReset().UseAlpha(false).DefaultValue( + Color_ImGuiToLUS(RAINBOW_PRESETS[0][1]))); + SohGui::mSohMenu->AddWidget(path, "Color 3", WIDGET_CVAR_COLOR_PICKER) + .CVar(CVAR("CustomRainbow3")) + .Options(UIWidgets::ColorPickerOptions().ShowRandom().ShowReset().UseAlpha(false).DefaultValue( + Color_ImGuiToLUS(RAINBOW_PRESETS[0][2]))); + SohGui::mSohMenu->AddWidget(path, "Color 4", WIDGET_CVAR_COLOR_PICKER) + .CVar(CVAR("CustomRainbow4")) + .Options(UIWidgets::ColorPickerOptions().ShowRandom().ShowReset().UseAlpha(false).DefaultValue( + Color_ImGuiToLUS(RAINBOW_PRESETS[0][3]))); + + SohGui::mSohMenu->AddWidget(path, "Presets", WIDGET_SEPARATOR_TEXT); + SohGui::mSohMenu->AddWidget(path, "Custom Rainbow Presets", WIDGET_CVAR_COMBOBOX) + .CVar(CVAR("CustomRainbowPreset")) + .Options(UIWidgets::ComboboxOptions().ComboMap(RAINBOW_PRESET_NAMES)) + .Callback([&](WidgetInfo& info) { + size_t rainbowPresetIdx = CVarGetInteger(CVAR("CustomRainbowPreset"), 0); + customColorZero = RAINBOW_PRESETS[rainbowPresetIdx][0]; + customColorOne = RAINBOW_PRESETS[rainbowPresetIdx][1]; + customColorMinusZero = RAINBOW_PRESETS[rainbowPresetIdx][2]; + customColorMinusOne = RAINBOW_PRESETS[rainbowPresetIdx][3]; + + CVarSetColor(CVAR("CustomRainbow1.Value"), Color_ImGuiToLUS(customColorZero)); + CVarSetColor(CVAR("CustomRainbow2.Value"), Color_ImGuiToLUS(customColorOne)); + CVarSetColor(CVAR("CustomRainbow3.Value"), Color_ImGuiToLUS(customColorMinusZero)); + CVarSetColor(CVAR("CustomRainbow4.Value"), Color_ImGuiToLUS(customColorMinusOne)); + CVarSave(); + }); + + SohGui::mSohMenu->AddWidget(path, "Do Something With Pots", WIDGET_CVAR_CHECKBOX).CVar(CVAR("DoSomethingWithPots")); +} + +// static RegisterShipInitFunc initFunc(OnConfigurationChanged); +// static RegisterMenuInitFunc menuInitFunc(RegisterMenu); diff --git a/soh/soh/Enhancements/Holiday/LL.h b/soh/soh/Enhancements/Holiday/LL.h new file mode 100644 index 00000000000..8a705c18836 --- /dev/null +++ b/soh/soh/Enhancements/Holiday/LL.h @@ -0,0 +1,71 @@ +#ifndef LL_H +#define LL_H + +#include +#include "soh/Enhancements/cosmetics/CosmeticsEditor.h" +#include "soh/Enhancements/game-interactor/GameInteractor.h" + +const size_t RAINBOW_PRESET_LEN = 9; + +std::unordered_map RAINBOW_PRESET_NAMES = { + { 0, "Christmas" }, { 1, "Transgender" }, { 2, "Nonbinary" }, { 3, "Bisexual" }, { 4, "Lesbian" }, + { 5, "Gay (MLM)" }, { 6, "Asexual" }, { 7, "Brazil" }, { 8, "Italy" } +}; + +static const ImVec4 RAINBOW_PRESETS[RAINBOW_PRESET_LEN][4] = { + { // christmas + { 0.0f / 255.0f, 140.0f / 255.0f, 69.0f / 255.0f, 1 }, + { 205.0f / 255.0f, 33.0f / 255.0f, 42.0f / 255.0f, 1 }, + { 0.0f / 255.0f, 140.0f / 255.0f, 69.0f / 255.0f, 1 }, + { 205.0f / 255.0f, 33.0f / 255.0f, 42.0f / 255.0f, 1 } }, + { // trans + { 255.0f / 255.0f, 255.0f / 255.0f, 255.0f / 255.0f, 1 }, + { 255.0f / 255.0f, 159.0f / 255.0f, 186.0f / 255.0f, 1 }, + { 255.0f / 255.0f, 255.0f / 255.0f, 255.0f / 255.0f, 1 }, + { 71.0f / 255.0f, 186.0f / 255.0f, 230.0f / 255.0f, 1 } }, + + { // enby + { 252.0f / 255.0f, 244.0f / 255.0f, 52.0f / 255.0f, 1 }, + { 255.0f / 255.0f, 255.0f / 255.0f, 255.0f / 255.0f, 1 }, + { 156.0f / 255.0f, 89.0f / 255.0f, 209.0f / 255.0f, 1 }, + { 0.0f / 255.0f, 0.0f / 255.0f, 0.0f / 255.0f, 1 } }, + + { // bi + { 155.0f / 255.0f, 79.0f / 255.0f, 150.0f / 255.0f, 1 }, + { 0.0f / 255.0f, 56.0f / 255.0f, 168.0f / 255.0f, 1 }, + { 155.0f / 255.0f, 79.0f / 255.0f, 150.0f / 255.0f, 1 }, + { 214.0f / 255.0f, 2.0f / 255.0f, 112.0f / 255.0f, 1 } }, + + { // lesbian + { 255.0f / 255.0f, 255.0f / 255.0f, 255.0f / 255.0f, 1 }, + { 213.0f / 255.0f, 45.0f / 255.0f, 0.0f / 255.0f, 1 }, + { 255.0f / 255.0f, 255.0f / 255.0f, 255.0f / 255.0f, 1 }, + { 163.0f / 255.0f, 2.0f / 255.0f, 98.0f / 255.0f, 1 } }, + + { // gay + { 7.0f / 255.0f, 141.0f / 255.0f, 112.0f / 255.0f, 1 }, + { 255.0f / 255.0f, 255.0f / 255.0f, 255.0f / 255.0f, 1 }, + { 123.0f / 255.0f, 173.0f / 255.0f, 226.0f / 255.0f, 1 }, + { 61.0f / 255.0f, 26.0f / 255.0f, 120.0f / 255.0f, 1 } }, + + { // ace + { 0.0f / 255.0f, 0.0f / 255.0f, 0.0f / 255.0f, 1 }, + { 163.0f / 255.0f, 163.0f / 255.0f, 163.0f / 255.0f, 1 }, + { 255.0f / 255.0f, 255.0f / 255.0f, 255.0f / 255.0f, 1 }, + { 128.0f / 255.0f, 0.0f / 255.0f, 128.0f / 255.0f, 1 } }, + + { // br + { 0.0f / 255.0f, 151.0f / 255.0f, 57.0f / 255.0f, 1 }, + { 254.0f / 255.0f, 221.0f / 255.0f, 0.0f / 255.0f, 1 }, + { 255.0f / 255.0f, 255.0f / 255.0f, 255.0f / 255.0f, 1 }, + { 1.0f / 255.0f, 33.0f / 255.0f, 105.0f / 255.0f, 1 } }, + + { // it + { 255.0f / 255.0f, 255.0f / 255.0f, 255.0f / 255.0f, 1 }, + { 0.0f / 255.0f, 140.0f / 255.0f, 69.0f / 255.0f, 1 }, + { 255.0f / 255.0f, 255.0f / 255.0f, 255.0f / 255.0f, 1 }, + { 205.0f / 255.0f, 33.0f / 255.0f, 42.0f / 255.0f, 1 } } + +}; + +#endif diff --git a/soh/soh/Enhancements/Holiday/NotProxySaw.cpp b/soh/soh/Enhancements/Holiday/NotProxySaw.cpp new file mode 100644 index 00000000000..b34bade0578 --- /dev/null +++ b/soh/soh/Enhancements/Holiday/NotProxySaw.cpp @@ -0,0 +1,1029 @@ +#include "Holiday.hpp" +#include +#include "soh/SohGui/UIWidgets.hpp" +#include "soh/Enhancements/game-interactor/GameInteractor.h" +#include "soh/Enhancements/custom-message/CustomMessageManager.h" +#include "include/message_data_fmt.h" +#include "soh/Network/Archipelago/Archipelago.h" + +extern "C" { +#include "macros.h" +#include "functions.h" +#include "variables.h" +extern PlayState* gPlayState; +} + +#define CVAR(v) "gHoliday.Gameplay." v + +int dialogIndex = 0; +int affection = 0; +int TARGET_AFFECTION = 10; +struct DialogOption { + std::string text; + int affectionChange; + int nextDialogIndex; +}; +struct Dialog { + std::string ganonText; + std::vector options; +}; + +const std::vector dialogs = { { // 0 + "Think you can defeat me? Foolish!", + { + { "You're lonely. You don't have to be.", 3, 1 }, + { "I'm here to end this... peacefully.", 1, 2 }, + { "I respect a man with ambition.", -1, 3 }, + } }, + { // 1 - Loneliness path + "Lonely? Power's my only ally.", + { + { "There's more to you than that.", 3, 4 }, + { "I get it more than you think.", 5, 6 }, + { "Why not let someone in?", 2, 5 }, + } }, + { // 2 - Peace path + "Peace? It's meaningless here.", + { + { "Maybe you've forgotten peace.", 2, 7 }, + { "Power isn't everything.", 4, 8 }, + { "Ally, not enemy.. that's my goal.", 1, 9 }, + } }, + { // 3 - Respect path + "Respect? From you?", + { + { "We're not so different.", 3, 10 }, + { "Maybe I admire your strength.", 5, 11 }, + { "True power must be earned.", -1, 12 }, + } }, + { // 4 - Early convergence + "You're... different than I thought.", + { + { "Together, we'd be unstoppable.", 4, 13 }, + { "Power won't bring fulfillment.", 3, 14 }, + { "Let's change the world.", 5, 15 }, + } }, + { // 5 - Vulnerability path + "No one has ever... understood before.", + { + { "I want to understand you.", 5, 16 }, + { "We all need someone.", 4, 17 }, + { "Your guard can come down with me.", 3, 18 }, + } }, + { // 6 - Shared experience path + "You think you know my struggle?", + { + { "I've faced darkness too.", 4, 19 }, + { "We're both outcasts.", 5, 20 }, + { "Tell me your story.", 3, 16 }, + } }, + { // 7 - Memory path + "Peace... I barely remember it.", + { + { "Let me remind you.", 5, 21 }, + { "We can create new memories.", 4, 22 }, + { "The desert remembers peace.", 2, 23 }, + } }, + { // 8 - Philosophy path + "Then what IS everything?", + { + { "Connection. Trust. Love.", 5, 24 }, + { "Finding your true purpose.", 3, 25 }, + { "Balance between power and heart.", 4, 26 }, + } }, + { // 9 - Alliance path + "An ally? What could you offer me?", + { + { "Companionship you've never known.", 5, 27 }, + { "A kingdom rebuilt together.", 3, 28 }, + { "Freedom from this endless cycle.", 4, 29 }, + } }, + { // 10 - Similarity path + "Similar? Explain yourself.", + { + { "We both fight for what we believe.", 3, 30 }, + { "We've both been betrayed.", 5, 31 }, + { "We're both stronger than we show.", 4, 32 }, + } }, + { // 11 - Admiration path + "Admire? This isn't a trick?", + { + { "Your determination is inspiring.", 5, 33 }, + { "You've overcome so much.", 4, 34 }, + { "I see the man behind the power.", 6, 35 }, + } }, + { // 12 - Challenge path (lower affection) + "Then prove you've earned it!", + { + { "I've earned the right to stand here.", 2, 36 }, + { "I'm not here to prove anything to you.", -2, 37 }, + { "My actions speak louder than words.", 3, 38 }, + } }, + { // 13 - Partnership power path + "Unstoppable... together?", + { + { "Your strength and my courage.", 5, 39 }, + { "We'd reshape Hyrule as equals.", 4, 40 }, + { "Imagine what we could achieve.", 3, 41 }, + } }, + { // 14 - Fulfillment path + "What could fulfill me then?", + { + { "Someone who sees you, truly.", 6, 42 }, + { "A purpose beyond conquest.", 3, 43 }, + { "The warmth of genuine connection.", 5, 44 }, + } }, + { // 15 - Revolution path + "Change the world... how?", + { + { "End this cycle of hatred.", 4, 45 }, + { "Build something beautiful together.", 5, 46 }, + { "Rewrite our destinies.", 6, 47 }, + } }, + { // 16 - Understanding convergence + "No one has ever tried to understand...", + { + { "I'm not no one. I'm here.", 6, 48 }, + { "Your past shaped you, not defined you.", 4, 49 }, + { "Let me be the first.", 5, 50 }, + } }, + { // 17 - Need path + "Need? I've survived alone this long.", + { + { "Surviving isn't living.", 5, 51 }, + { "You don't have to anymore.", 6, 52 }, + { "Even the strongest need support.", 4, 53 }, + } }, + { // 18 - Guard down path + "Why would I lower my guard?", + { + { "Because I'm lowering mine first.", 6, 54 }, + { "Trust has to start somewhere.", 4, 55 }, + { "You're safe with me.", 5, 56 }, + } }, + { // 19 - Darkness shared path + "Your darkness... tell me.", + { + { "I've fought my own demons.", 5, 57 }, + { "We all have shadows within us.", 4, 58 }, + { "But I chose light. You can too.", 3, 59 }, + } }, + { // 20 - Outcast bond path + "Outcasts... yes. We are.", + { + { "Then let's find belonging in each other.", 7, 60 }, + { "We don't have to be alone.", 5, 61 }, + { "Our shared pain connects us.", 4, 62 }, + } }, + { // 21 - Reminder path + "How could you remind me?", + { + { "Close your eyes. Remember the desert wind.", 5, 63 }, + { "Through moments like this one.", 6, 64 }, + { "Let me show you, not tell you.", 4, 65 }, + } }, + { // 22 - New memories path + "New memories... with you?", + { + { "Every ending is a new beginning.", 6, 66 }, + { "Yes. Better ones than you've known.", 5, 67 }, + { "We write our own story from here.", 4, 68 }, + } }, + { // 23 - Desert memories path + "The desert... my home once.", + { + { "It can be again, differently.", 4, 69 }, + { "Home is where the heart is.", 5, 70 }, + { "I'd like to see it with you.", 6, 71 }, + } }, + { // 24 - Love path (high affection) + "Love? You speak of... love?", + { + { "Is that so impossible?", 6, 72 }, + { "I think I'm falling for you.", 8, 73 }, + { "Love is the strongest power.", 5, 74 }, + } }, + { // 25 - Purpose path + "My purpose was always power.", + { + { "You're meant for more.", 5, 75 }, + { "Purpose can evolve.", 4, 76 }, + { "Find purpose in protecting, not destroying.", 3, 77 }, + } }, + { // 26 - Balance path + "Balance... I've never had that.", + { + { "I'll help you find it.", 6, 78 }, + { "We can balance each other.", 7, 79 }, + { "It's not too late to learn.", 4, 80 }, + } }, + { // 27 - Companionship offer + "Companionship? What does that mean?", + { + { "It means you're not alone anymore.", 7, 81 }, + { "Someone to share victories and defeats.", 5, 82 }, + { "A partner in every sense.", 6, 83 }, + } }, + { // 28 - Kingdom rebuild path + "Rebuild? After all I've destroyed?", + { + { "Redemption is always possible.", 5, 84 }, + { "We build on lessons learned.", 4, 85 }, + { "Your past doesn't define your future.", 6, 86 }, + } }, + { // 29 - Freedom path + "Freedom from the cycle...", + { + { "Break free with me.", 7, 87 }, + { "We choose our own fate.", 6, 88 }, + { "This doesn't have to be our story.", 5, 89 }, + } }, + { // 30 - Belief path + "You fight for Hyrule. I fight for...", + { + { "For recognition. I see you.", 6, 90 }, + { "We can fight for each other now.", 7, 91 }, + { "What we fight for can change.", 4, 92 }, + } }, + { // 31 - Betrayal bond path + "Betrayed... yes. You know that pain?", + { + { "More than you realize.", 6, 93 }, + { "But I won't betray you.", 7, 94 }, + { "We can heal together.", 5, 95 }, + } }, + { // 32 - Hidden strength path + "Stronger than I show?", + { + { "Your vulnerability is strength.", 6, 96 }, + { "True strength is opening your heart.", 7, 97 }, + { "I see both your power and pain.", 5, 98 }, + } }, + { // 33 - Inspiration path + "You're... inspired by me?", + { + { "Your resolve never wavered.", 6, 99 }, + { "Despite everything, you stood tall.", 7, 100 }, + { "That kind of strength is rare.", 5, 101 }, + } }, + { // 34 - Overcome path + "I've overcome... so much pain.", + { + { "And you don't have to face more alone.", 7, 102 }, + { "Let me help carry that burden.", 6, 103 }, + { "Your journey shaped a remarkable person.", 5, 104 }, + } }, + { // 35 - True sight path (very high affection) + "You... you see me? The real me?", + { + { "Every part of you.", 8, 105 }, + { "Behind the armor and anger, yes.", 7, 106 }, + { "And I want to know you more.", 6, 107 }, + } }, + { // 36 - Earned respect path + "Perhaps you have earned something...", + { + { "Your respect means everything.", 5, 108 }, + { "I've earned a chance.", 4, 109 }, + { "Maybe even your trust?", 6, 110 }, + } }, + { // 37 - Confrontation path (recovery possible) + "Arrogant! Just like I thought!", + { + { "Wait, I didn't mean it like that.", 3, 111 }, + { "You're right, I'm sorry.", 4, 112 }, + { "Let me start over.", 2, 113 }, + } }, + { // 38 - Action path + "Actions... like coming here unarmed?", + { + { "I'm armed with only honesty.", 6, 114 }, + { "My sword isn't drawn.", 5, 115 }, + { "I chose words over weapons.", 7, 116 }, + } }, + { // 39 - Combined strength + "My strength and your courage...", + { + { "We'd be legendary.", 6, 117 }, + { "Nothing could stand in our way.", 5, 118 }, + { "But more than that, we'd have each other.", 8, 119 }, + } }, + { // 40 - Equals path + "Equals? You'd see me as equal?", + { + { "Never as less than.", 7, 81 }, + { "Partners in every way.", 8, 83 }, + { "Two halves of a greater whole.", 6, 79 }, + } }, + { // 41 - Achievement dreams + "I've imagined... but never with another.", + { + { "Dream with me now.", 7, 66 }, + { "Our achievements, together.", 6, 82 }, + { "Let's make it real.", 8, 117 }, + } }, + { // 42 - True sight deep + "Someone who sees me truly...", + { + { "I see your scars and your dreams.", 8, 105 }, + { "Every layer, every truth.", 7, 106 }, + { "And I'm not afraid.", 9, 54 }, + } }, + { // 43 - New purpose + "A purpose beyond conquest...", + { + { "Building instead of destroying.", 6, 84 }, + { "Creating a legacy of hope.", 7, 86 }, + { "Finding joy in creation.", 5, 76 }, + } }, + { // 44 - Connection warmth + "Warmth... I've been cold so long.", + { + { "Let me warm your heart.", 9, 65 }, + { "You don't have to be cold anymore.", 8, 52 }, + { "I'll be your warmth.", 10, 56 }, + } }, + { // 45 - End hatred + "End the hatred... can it be done?", + { + { "With you by my side, yes.", 8, 88 }, + { "We start by choosing love.", 9, 74 }, + { "Together, we break the cycle.", 7, 87 }, + } }, + { // 46 - Build beauty + "Something beautiful... with you?", + { + { "The most beautiful thing.", 9, 73 }, + { "A future worth living for.", 8, 86 }, + { "Our love could heal Hyrule.", 10, 95 }, + } }, + { // 47 - Rewrite destiny + "Rewrite our destinies... together...", + { + { "Our story, our way.", 9, 68 }, + { "No prophecy can stop us.", 8, 88 }, + { "Destiny is what we make it.", 10, 119 }, + } }, + { // 48 - Here for you + "You're here... for me?", + { + { "Only for you.", 9, 73 }, + { "I chose you, Ganondorf.", 10, 94 }, + { "And I'm not leaving.", 8, 52 }, + } }, + { // 49 - Past vs present + "Shaped but not defined...", + { + { "You define yourself now.", 8, 76 }, + { "Your future is unwritten.", 7, 86 }, + { "Define yourself through love.", 9, 74 }, + } }, + { // 50 - First to understand + "The first to try...", + { + { "And I'll never stop trying.", 10, 103 }, + { "You deserve understanding.", 8, 95 }, + { "Let me be your first and last.", 9, 61 }, + } }, + { // 51 - Living vs surviving + "Living... what is that like?", + { + { "Let me show you.", 9, 65 }, + { "It's everything you've missed.", 8, 64 }, + { "We'll discover it together.", 10, 66 }, + } }, + { // 52 - No more alone + "I don't... have to be alone?", + { + { "Never again.", 10, 81 }, + { "I'm here, always.", 9, 102 }, + { "We'll face everything together.", 8, 53 }, + } }, + { // 53 - Strongest need support + "Even the strongest...", + { + { "Especially the strongest.", 8, 103 }, + { "Let me be your strength.", 9, 97 }, + { "We're stronger together.", 10, 119 }, + } }, + { // 54 - Mutual vulnerability + "You'd... lower yours first?", + { + { "I already have.", 10, 94 }, + { "My heart is open to you.", 9, 97 }, + { "I trust you completely.", 11, 110 }, + } }, + { // 55 - Trust begins + "Trust... starting here?", + { + { "Right here, right now.", 9, 64 }, + { "With us, with this moment.", 10, 94 }, + { "I trust you with everything.", 8, 110 }, + } }, + { // 56 - Safe haven + "Safe... with you?", + { + { "Always. I promise.", 11, 94 }, + { "I'll protect your heart.", 10, 103 }, + { "You're safe in my arms.", 12, 60 }, + } }, + { // 57 - Shared demons + "Your demons... like mine?", + { + { "We can fight them together.", 9, 53 }, + { "They don't control us anymore.", 8, 59 }, + { "Love conquers all darkness.", 10, 74 }, + } }, + { // 58 - Universal shadows + "Shadows within us all...", + { + { "But you bring light to mine.", 10, 63 }, + { "Let's be each other's light.", 11, 79 }, + { "Together we shine brighter.", 9, 118 }, + } }, + { // 59 - Choose light + "I could... choose light?", + { + { "You already are, by listening.", 9, 48 }, + { "Choose me, choose light.", 10, 88 }, + { "It's never too late.", 8, 80 }, + } }, + { // 60 - Belonging together + "Belonging... in each other...", + { + { "You belong with me.", 12, 83 }, + { "We're home in each other.", 11, 119 }, + { "My heart is your home.", 13, 119 }, + } }, + { // 61 - Together forever + "Not alone... anymore...", + { + { "Never, ever alone.", 11, 102 }, + { "I'll always be here.", 10, 119 }, + { "Forever together.", 12, 119 }, + } }, + { // 62 - Connected pain + "Our shared pain connects us...", + { + { "Now let joy connect us.", 10, 117 }, + { "Pain brought us here, love keeps us.", 11, 95 }, + { "From pain to paradise.", 9, 51 }, + } }, + { // 63 - Desert meditation + "The desert wind... I remember...", + { + { "Hold onto that feeling.", 9, 64 }, + { "Peace can return.", 10, 69 }, + { "I'll be your peace.", 11, 56 }, + } }, + { // 64 - This moment + "This moment... it feels...", + { + { "Like coming home?", 10, 79 }, + { "Like the start of something beautiful?", 11, 66 }, + { "Right. It feels right.", 12, 79 }, + } }, + { // 65 - Show not tell + "Show me... how?", + { + { "Through actions, through devotion.", 10, 114 }, + { "Every day, every moment.", 11, 102 }, + { "Let me love you.", 12, 73 }, + } }, + { // 66 - New beginning + "A new beginning... with you...", + { + { "Our greatest adventure.", 11, 117 }, + { "Better than any ending.", 10, 117 }, + { "The best is yet to come.", 12, 89 }, + } }, + { // 67 - Better memories + "Better memories... I want that.", + { + { "Then take my hand.", 12, 119 }, + { "We'll make them together.", 11, 68 }, + { "Starting right now.", 10, 64 }, + } }, + { // 68 - Write our story + "Our story... from here...", + { + { "The greatest story ever told.", 11, 117 }, + { "Written in love, not war.", 12, 116 }, + { "A story for the ages.", 10, 89 }, + } }, + { // 69 - Home differently + "Home, differently... explain.", + { + { "With love, not loneliness.", 10, 119 }, + { "Together, not alone.", 11, 119 }, + { "Our home, our rules.", 9, 71 }, + } }, + { // 70 - Heart is home + "Where the heart is...", + { + { "My heart is with you.", 12, 83 }, + { "You are my home.", 13, 119 }, + { "Home is in your eyes.", 11, 105 }, + } }, + { // 71 - See together + "You'd... visit my homeland with me?", + { + { "Anywhere you go, I go.", 12, 61 }, + { "I want to see your world.", 11, 107 }, + { "Your past is part of our future.", 10, 86 }, + } }, + { // 72 - Love not impossible + "Not impossible... but unexpected.", + { + { "The best things are unexpected.", 11, 117 }, + { "Sometimes fate surprises us.", 10, 88 }, + { "I love you, Ganondorf.", 13, 73 }, + } }, + { // 73 - Falling confession + "You're... falling for me?", + { + { "I've already fallen.", 14, -1 }, + { "Completely, utterly, truly.", 13, -1 }, + { "Catch me?", 12, -1 }, + } }, + { // 74 - Love strongest power + "Love... the strongest power...", + { + { "And it's ours to share.", 12, 119 }, + { "More powerful than the Triforce.", 13, -1 }, + { "Let love be our strength.", 11, 97 }, + } }, + { // 75 - Meant for more + "Meant for more... perhaps...", + { + { "I know you are.", 10, 76 }, + { "You're meant for greatness and love.", 11, 101 }, + { "Let me prove it to you.", 9, 65 }, + } }, + { // 76 - Purpose evolves + "Purpose can evolve...", + { + { "Like we evolve together.", 11, 78 }, + { "Grow with me.", 10, 117 }, + { "Our purpose is each other.", 12, 83 }, + } }, + { // 77 - Protect not destroy + "Protect... instead of destroy...", + { + { "Protect what we build together.", 10, 84 }, + { "Protect each other's hearts.", 11, 103 }, + { "I'll teach you, if you teach me.", 9, 78 }, + } }, + { // 78 - Help find balance + "You'll help me find it?", + { + { "Every step of the way.", 11, 102 }, + { "We'll find it together.", 10, 79 }, + { "I'm already helping, see?", 12, 64 }, + } }, + { // 79 - Balance each other + "Balance each other...", + { + { "Perfect harmony.", 12, 117 }, + { "Yin and yang.", 11, 119 }, + { "Two souls, one heart.", 13, 119 }, + } }, + { // 80 - Never too late + "Never too late... truly?", + { + { "For love? Never.", 12, 119 }, + { "You're here now, that's what matters.", 11, 64 }, + { "This is your moment.", 10, 88 }, + } }, + { // 81 - Not alone anymore + "Not alone... anymore...", + { + { "You have me, always.", 12, 102 }, + { "We have each other.", 13, 119 }, + { "Forever and always.", 11, 119 }, + } }, + { // 82 - Share everything + "Share victories and defeats...", + { + { "Everything, together.", 11, 119 }, + { "In good times and bad.", 12, 83 }, + { "Till the end of time.", 10, 119 }, + } }, + { // 83 - Partner every sense + "Partner in every sense...", + { + { "In battle and in love.", 12, 91 }, + { "Soul mates.", 13, 119 }, + { "My other half.", 14, -1 }, + } }, + { // 84 - Redemption possible + "Redemption... for me?", + { + { "For everyone. Especially you.", 11, 95 }, + { "I believe in you.", 12, 109 }, + { "Love redeems all.", 10, 74 }, + } }, + { // 85 - Build on lessons + "Lessons learned... yes...", + { + { "Wisdom through experience.", 10, 76 }, + { "Our past guides our future.", 11, 86 }, + { "Together we're wiser.", 9, 78 }, + } }, + { // 86 - Future undefined + "My future... undefined...", + { + { "Let's define it together.", 12, 89 }, + { "A blank canvas for us to paint.", 11, 68 }, + { "Our future is love.", 13, 119 }, + } }, + { // 87 - Break free together + "Break free... with you...", + { + { "Hand in hand.", 13, 119 }, + { "Into a new dawn.", 12, 117 }, + { "Freedom through love.", 14, -1 }, + } }, + { // 88 - Choose fate + "We choose... our own fate...", + { + { "And I choose you.", 14, -1 }, + { "Our fate is our love.", 13, 119 }, + { "Destiny be damned.", 12, 89 }, + } }, + { // 89 - Not our story + "This doesn't have to be our story...", + { + { "We write a better one.", 13, 117 }, + { "Our love story.", 14, -1 }, + { "A story of hope.", 12, 117 }, + } }, + { // 90 - Recognition found + "You... see me...", + { + { "I see you, I love you.", 13, 105 }, + { "All of you.", 12, 98 }, + { "And I always will.", 14, -1 }, + } }, + { // 91 - Fight for each other + "Fight for each other...", + { + { "With each other.", 13, 119 }, + { "Side by side.", 12, 117 }, + { "Because of each other.", 14, -1 }, + } }, + { // 92 - What we fight changes + "What we fight for... changes...", + { + { "Love changes everything.", 13, 95 }, + { "You've changed me.", 12, 104 }, + { "We've changed each other.", 14, -1 }, + } }, + { // 93 - Know betrayal pain + "You know that pain...", + { + { "But we heal together.", 12, 95 }, + { "No more betrayal, only trust.", 13, 110 }, + { "Our bond is unbreakable.", 11, 94 }, + } }, + { // 94 - Won't betray promise + "You won't... betray me?", + { + { "Never. I swear it.", 14, -1 }, + { "You have my word, my heart.", 13, 119 }, + { "I'd sooner die.", 12, 102 }, + } }, + { // 95 - Heal together + "Heal... together...", + { + { "Our love is the remedy.", 13, 119 }, + { "Time and trust heal all.", 12, 110 }, + { "I'll help you heal.", 14, -1 }, + } }, + { // 96 - Vulnerability strength + "Vulnerability... is strength?", + { + { "The greatest strength.", 12, 101 }, + { "It takes courage to be vulnerable.", 13, 99 }, + { "With me, you can be vulnerable.", 14, -1 }, + } }, + { // 97 - Open heart strength + "Opening my heart...", + { + { "Is the bravest thing you'll do.", 13, 99 }, + { "I'll cherish it always.", 14, -1 }, + { "Your heart is safe with me.", 15, -1 }, + } }, + { // 98 - Power and pain + "My power and pain...", + { + { "Both make you who you are.", 12, 104 }, + { "I love all of you.", 14, -1 }, + { "Let me ease your pain.", 13, 95 }, + } }, + { // 99 - Unwavering resolve + "My resolve... you noticed?", + { + { "How could I not?", 12, 100 }, + { "It's magnificent.", 13, 101 }, + { "It's one of many things I love.", 14, -1 }, + } }, + { // 100 - Stood tall + "I stood tall... yes...", + { + { "And you still stand tall.", 13, 101 }, + { "Now stand with me.", 14, -1 }, + { "Together we stand taller.", 12, 118 }, + } }, + { // 101 - Rare strength + "Rare strength...", + { + { "Matched only by your capacity to love.", 13, 104 }, + { "Strength I admire and love.", 14, -1 }, + { "Let me be worthy of it.", 12, 109 }, + } }, + { // 102 - No more alone + "No more... alone...", + { + { "Not while I breathe.", 14, -1 }, + { "I'm here, forever.", 15, -1 }, + { "Always together.", 13, 119 }, + } }, + { // 103 - Carry burden + "Help carry... my burden?", + { + { "I'd carry it all if I could.", 14, -1 }, + { "Your burden is my burden.", 13, 119 }, + { "Share it with me.", 15, -1 }, + } }, + { // 104 - Remarkable person + "Remarkable... person?", + { + { "The most remarkable I've known.", 14, -1 }, + { "You're extraordinary.", 15, -1 }, + { "And you're mine.", 13, 119 }, + } }, + { // 105 - Every part + "Every part... of me?", + { + { "Every. Single. Part.", 16, -1 }, + { "The good, the bad, all of it.", 15, -1 }, + { "I love all of you.", 17, -1 }, + } }, + { // 106 - Behind armor + "Behind the armor... you see...", + { + { "The man I love.", 16, -1 }, + { "Your true self.", 15, -1 }, + { "The one meant for me.", 17, -1 }, + } }, + { // 107 - Know more + "Know me more...", + { + { "Spend eternity learning you.", 16, -1 }, + { "Every day, something new.", 15, -1 }, + { "I'll never stop discovering you.", 17, -1 }, + } }, + { // 108 - Respect means everything + "Means everything...", + { + { "Then have all of it.", 13, 90 }, + { "You have my respect and love.", 14, -1 }, + { "You've earned both.", 12, 109 }, + } }, + { // 109 - Earned chance + "A chance... yes...", + { + { "A chance at love.", 13, 72 }, + { "A chance at happiness.", 14, -1 }, + { "Take it. Take me.", 15, -1 }, + } }, + { // 110 - Maybe trust + "Maybe... even trust...", + { + { "Especially trust.", 14, -1 }, + { "Trust me with your heart.", 15, -1 }, + { "I trust you with mine.", 13, 119 }, + } }, + { // 111 - Clarification + "You didn't mean...", + { + { "I meant I admire you.", 11, 33 }, + { "Let me explain better.", 10, 48 }, + { "I'm nervous around you.", 12, 72 }, + } }, + { // 112 - Apology path + "You're... sorry?", + { + { "Deeply. I care about you.", 12, 48 }, + { "I don't want to fight.", 11, 45 }, + { "I want to love you.", 13, 72 }, + } }, + { // 113 - Start over + "Start over...", + { + { "Hi. I'm Link. And I love you.", 13, 73 }, + { "Let me show you my heart.", 12, 97 }, + { "Give me another chance?", 11, 109 }, + } }, + { // 114 - Armed with honesty + "Honesty... that's rare.", + { + { "I'll always be honest with you.", 13, 94 }, + { "Honesty and love.", 14, -1 }, + { "The truth is I love you.", 12, 73 }, + } }, + { // 115 - Sword not drawn + "Your sword... sheathed...", + { + { "I don't need it with you.", 13, 54 }, + { "My heart is my weapon now.", 14, -1 }, + { "I fight for you, not against you.", 12, 91 }, + } }, + { // 116 - Words over weapons + "Words over weapons...", + { + { "Words of love.", 14, -1 }, + { "The pen is mightier, they say.", 12, 114 }, + { "Let love be our language.", 15, -1 }, + } }, + { // 117 - Legendary + "Legendary... yes...", + { + { "A legend of love.", 14, -1 }, + { "Our legend.", 15, -1 }, + { "Written in the stars.", 13, 119 }, + } }, + { // 118 - Nothing in way + "Nothing could stand...", + { + { "Against our love.", 14, -1 }, + { "We're unstoppable together.", 13, 119 }, + { "Invincible.", 15, -1 }, + } }, + { // 119 - Have each other (high affection) + "We'd have each other...", + { + { "That's all that matters.", 17, -1 }, + { "The greatest treasure.", 16, -1 }, + { "My heart is yours.", 18, -1 }, + } } }; + +static void OnConfigurationChanged() { + COND_ID_HOOK(OnActorInit, ACTOR_BOSS_GANON, CVarGetInteger(CVAR("GanonDatingSim"), 0), [](void* actorRef) { + dialogIndex = 0; + affection = 0; + }); + + COND_VB_SHOULD(VB_GANONDORF_DECIDE_TO_FIGHT, CVarGetInteger(CVAR("GanonDatingSim"), 0), { + MessageContext* msgCtx = &gPlayState->msgCtx; + + if (dialogIndex == -1) { + if (affection >= TARGET_AFFECTION) { + gSaveContext.ship.stats.itemTimestamp[TIMESTAMP_DEFEAT_GANON] = GAMEPLAYSTAT_TOTAL_TIME; + gSaveContext.ship.stats.gameComplete = true; + ArchipelagoClient::GetInstance().SendGameWon(); + + gPlayState->nextEntranceIndex = ENTR_CHAMBER_OF_THE_SAGES_0; + gSaveContext.nextCutsceneIndex = 0xFFF2; + gPlayState->transitionTrigger = TRANS_TRIGGER_START; + gPlayState->transitionType = TRANS_TYPE_FADE_WHITE; + GameInteractor::State::TriforceHuntCreditsWarpActive = 0; + *should = false; + return; + } + return; + } + + affection += dialogs[dialogIndex].options[msgCtx->choiceIndex].affectionChange; + dialogIndex = dialogs[dialogIndex].options[msgCtx->choiceIndex].nextDialogIndex; + + *should = false; + Message_StartTextbox(gPlayState, 0x70CB, NULL); + }); + + COND_ID_HOOK( + OnOpenText, 0x70CB, CVarGetInteger(CVAR("GanonDatingSim"), 0), [](u16* textId, bool* loadFromMessageTable) { + std::string message; + if (dialogIndex == -1) { + if (affection >= TARGET_AFFECTION) { + message = "I've never felt this way before...\x01Take my power, Link. I trust you."; + } else { + message = "Liar! You're just like the rest of\x01them! Now I must destroy you!"; + } + } else { + message = dialogs[dialogIndex].ganonText + "\x01\x1C" + dialogs[dialogIndex].options[0].text + "\x01" + + dialogs[dialogIndex].options[1].text + "\x01" + dialogs[dialogIndex].options[2].text; + } + + auto messageEntry = CustomMessage(message); + messageEntry.Format(); + messageEntry.LoadIntoFont(); + *loadFromMessageTable = false; + }); +} + +static void RegisterMenu() { + WidgetPath path = { "Holiday", "Gameplay", SECTION_COLUMN_1 }; + + SohGui::mSohMenu->AddWidget(path, "Ganon Dating Sim", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR("GanonDatingSim")) + .Options(UIWidgets::CheckboxOptions().Tooltip("Prior to fighting him at the top of his Castle, you make an " + "attempt to convince Ganon to join you instead.")); +} + +static RegisterShipInitFunc initFunc(OnConfigurationChanged, { CVAR("GanonDatingSim") }); +// static RegisterShipInitFunc initFunc2([]() { +// // Validate dialog tree doesn't have any cycles, invalid indices, unreachable nodes, or dead ends. +// std::set reachableNodes; +// std::set visitedInPath; + +// // Check for invalid indices and cycles using DFS +// std::function&)> validateNode = [&](int nodeIndex, std::set& currentPath) -> bool { +// if (nodeIndex == -1) return true; // Valid end + +// if (nodeIndex < 0 || nodeIndex >= (int)dialogs.size()) { +// SPDLOG_ERROR("[Ganon Dating Sim] Invalid dialog index: {}", nodeIndex); +// return false; +// } + +// if (currentPath.count(nodeIndex)) { +// SPDLOG_ERROR("[Ganon Dating Sim] Cycle detected at node {}", nodeIndex); +// return false; +// } + +// reachableNodes.insert(nodeIndex); +// currentPath.insert(nodeIndex); + +// const auto& dialog = dialogs[nodeIndex]; +// if (dialog.options.size() != 3) { +// SPDLOG_ERROR("[Ganon Dating Sim] Node {} doesn't have exactly 3 options", nodeIndex); +// return false; +// } + +// for (const auto& option : dialog.options) { +// if (!validateNode(option.nextDialogIndex, currentPath)) { +// return false; +// } +// } + +// currentPath.erase(nodeIndex); +// return true; +// }; + +// // Start validation from root node (0) +// std::set path; +// if (!validateNode(0, path)) { +// SPDLOG_ERROR("[Ganon Dating Sim] Dialog tree validation failed!"); +// } + +// // Check for unreachable nodes +// for (size_t i = 0; i < dialogs.size(); i++) { +// if (reachableNodes.count(i) == 0) { +// SPDLOG_WARN("[Ganon Dating Sim] Node {} is unreachable from root", i); +// } +// } + +// // Simulate all possible paths to check for dead ends (paths that don't reach affection >= TARGET_AFFECTION) +// std::function&, int)> simulatePath = [&](int nodeIndex, int currentAffection, +// std::vector& path, int depth) { +// if (depth > 100) { // Prevent infinite loops in case validation missed something +// std::string pathStr = ""; +// for (size_t i = 0; i < path.size() && i < 20; i++) { +// pathStr += std::to_string(path[i]) + " -> "; +// } +// if (path.size() > 20) pathStr += "... -> "; +// pathStr += std::to_string(nodeIndex); +// SPDLOG_WARN("[Ganon Dating Sim] Path too deep (possible cycle): depth={}, path: {}", depth, pathStr); +// throw std::runtime_error("Path too deep (possible cycle) in Ganon Dating Sim dialog tree"); +// } + +// if (nodeIndex == -1) { +// // Reached an ending +// if (currentAffection < TARGET_AFFECTION) { +// SPDLOG_DEBUG("[Ganon Dating Sim] Found path with insufficient affection: {} (need {})", +// currentAffection, TARGET_AFFECTION); +// } +// return; +// } + +// if (nodeIndex < 0 || nodeIndex >= (int)dialogs.size()) { +// return; // Already validated above +// } + +// path.push_back(nodeIndex); +// const auto& dialog = dialogs[nodeIndex]; + +// for (const auto& option : dialog.options) { +// std::vector newPath = path; +// simulatePath(option.nextDialogIndex, currentAffection + option.affectionChange, newPath, depth + 1); +// } +// }; + +// // Run simulation from root to find all possible endings +// std::vector initialPath; +// simulatePath(0, 0, initialPath, 0); + +// SPDLOG_INFO("[Ganon Dating Sim] Dialog tree validation complete. {} nodes reachable.", reachableNodes.size()); +// }, {}); +static RegisterMenuInitFunc menuInitFunc(RegisterMenu); diff --git a/soh/soh/Enhancements/Holiday/Pablo.cpp b/soh/soh/Enhancements/Holiday/Pablo.cpp new file mode 100644 index 00000000000..9d92bfca817 --- /dev/null +++ b/soh/soh/Enhancements/Holiday/Pablo.cpp @@ -0,0 +1,155 @@ +#include "Holiday.hpp" +#include "z64.h" +#include "macros.h" +#include "functions.h" +#include "src/overlays/effects/ovl_Effect_Ss_En_Ice/z_eff_ss_en_ice.h" + +extern "C" { +extern PlayState* gPlayState; +} + +#define CVAR(v) "gHoliday.Gameplay." v + +#pragma region Shiny + +static Vec3f shineSpots[12] = { + { 20.0f, 20.0f, 0.0f }, { 10.0f, 40.0f, 10.0f }, { -10.0f, 40.0f, 10.0f }, { -20.0f, 20.0f, 0.0f }, + { 10.0f, 40.0f, -10.0f }, { -10.0f, 40.0f, -10.0f }, { 0.0f, 20.0f, -20.0f }, { 10.0f, 0.0f, 10.0f }, + { 10.0f, 0.0f, -10.0f }, { 0.0f, 20.0f, 20.0f }, { -10.0f, 0.0f, 10.0f }, { -10.0f, 0.0f, -10.0f }, +}; + +static u8 shinyableActorIds[51] = { + ACTOR_EN_WALLMAS, ACTOR_EN_ZF, ACTOR_EN_YUKABYUN, ACTOR_EN_WF, ACTOR_EN_WEIYER, ACTOR_EN_VM, + ACTOR_EN_TUBO_TRAP, ACTOR_EN_VALI, ACTOR_EN_TP, ACTOR_EN_TORCH2, ACTOR_EN_TITE, ACTOR_EN_TEST, + ACTOR_EN_SW, ACTOR_EN_ST, ACTOR_EN_SKB, ACTOR_EN_SKJ, ACTOR_EN_PEEHAT, ACTOR_EN_SB, + ACTOR_EN_RR, ACTOR_EN_REEBA, ACTOR_EN_RD, ACTOR_EN_PO_SISTERS, ACTOR_EN_PO_FIELD, ACTOR_EN_POH, + ACTOR_EN_KAREBABA, ACTOR_EN_OKUTA, ACTOR_EN_NY, ACTOR_EN_MB, ACTOR_EN_IK, ACTOR_EN_GOMA, + ACTOR_EN_GELDB, ACTOR_EN_FZ, ACTOR_EN_FLOORMAS, ACTOR_EN_FIREFLY, ACTOR_EN_FD, ACTOR_EN_EIYER, + ACTOR_EN_DODONGO, ACTOR_EN_DODOJR, ACTOR_EN_DH, ACTOR_EN_DEKUBABA, ACTOR_EN_CROW, ACTOR_EN_CLEAR_TAG, + ACTOR_EN_BW, ACTOR_EN_BUBBLE, ACTOR_EN_AM, ACTOR_EN_BILI, ACTOR_EN_BIGOKUTA, ACTOR_EN_BB, + ACTOR_EN_BA, ACTOR_EN_ANUBICE, ACTOR_DOOR_KILLER +}; + +u8 CanBeShiny(Actor* actor) { + for (u8 i = 0; i < ARRAY_COUNT(shinyableActorIds); i += 1) { + if (shinyableActorIds[i] == actor->id) { + return true; + } + } + return false; +} + +void ApplyShinyness(Actor* actor) { + if (!CanBeShiny(actor)) { + assert(false); + return; + } + + actor->isShiny = true; + actor->colChkInfo.health *= 4; + actor->maximumHealth *= 4; + actor->scale.x *= 1.25f; + actor->scale.y *= 1.25f; + actor->scale.z *= 1.25f; +} + +void RenderShines(Actor* actor) { + if (!CanBeShiny(actor) || !actor->isShiny) { + assert(false); + return; + } + + if (gSaveContext.gameMode != GAMEMODE_NORMAL || gPlayState->pauseCtx.state != 0) { + return; + } + + if (Rand_ZeroOne() < 0.1f) { + Vec3f shinePos; + s32 i = (s32)(Rand_ZeroOne() * ARRAY_COUNT(shineSpots)); + + shinePos.x = actor->world.pos.x + shineSpots[i].x; + shinePos.y = actor->world.pos.y + shineSpots[i].y; + shinePos.z = actor->world.pos.z + shineSpots[i].z; + + EffectSsEnIceInitParams initParams; + + initParams.actor = actor; + initParams.pos = shinePos; + initParams.type = 0; + + // 50/50 chance of red or green + if (Rand_Next() % 2 == 0) { + initParams.primColor.r = 255; + initParams.primColor.g = 0; + initParams.envColor.r = 255; + initParams.envColor.g = 0; + } else { + initParams.primColor.r = 0; + initParams.primColor.g = 255; + initParams.envColor.r = 0; + initParams.envColor.g = 255; + } + + initParams.primColor.b = 0; + initParams.primColor.a = 250; + initParams.envColor.b = 0; + initParams.scale = 0.5f; + + EffectSs_Spawn(gPlayState, EFFECT_SS_EN_ICE, 80, &initParams); + } +} + +void SpawnShinyReward(Actor* actor) { + if (!CanBeShiny(actor)) { + assert(false); + return; + } + + for (u8 i = 0; i < 10; i += 1) { + Item_DropCollectible(gPlayState, &actor->world.pos, ITEM00_RUPEE_RED); + Item_DropCollectible(gPlayState, &actor->world.pos, ITEM00_RUPEE_GREEN); + } +} + +void RegisterShiny() { + COND_HOOK(OnActorInit, CVarGetInteger(CVAR("Shiny.Enabled"), 0), [](void* refActor) { + Actor* actor = static_cast(refActor); + if (CanBeShiny(actor) && Rand_ZeroOne() < (1.0f / (s32)CVarGetInteger(CVAR("Shiny.Chance"), 8192))) { + ApplyShinyness(actor); + } + }); + + COND_HOOK(OnEnemyDefeat, CVarGetInteger(CVAR("Shiny.Enabled"), 0), [](void* refActor) { + Actor* actor = static_cast(refActor); + if (actor->isShiny) { + SpawnShinyReward(actor); + } + }); + + COND_HOOK(OnActorDraw, CVarGetInteger(CVAR("Shiny.Enabled"), 0), [](void* refActor) { + Actor* actor = static_cast(refActor); + if (actor->isShiny) { + RenderShines(actor); + } + }); +} + +#pragma endregion + +static void RegisterMenu() { + WidgetPath path = { "Holiday", "Gameplay", SECTION_COLUMN_2 }; + SohGui::mSohMenu->AddWidget(path, "Shiny Enemies", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR("Shiny.Enabled")) + .Options(UIWidgets::CheckboxOptions().Tooltip( + "Allows enemies to be shiny.\nShiny enemies are 25% bigger and have 4 times the health but drop " + "the equivalent of a gold rupee upon death")); + + SohGui::mSohMenu->AddWidget(path, "Shiny Chance: %d", WIDGET_CVAR_SLIDER_INT) + .CVar(CVAR("Shiny.Chance")) + .PreFunc([](WidgetInfo& info) { info.options.get()->disabled = !CVarGetInteger(CVAR("Shiny.Enabled"), 0); }) + .Options(UIWidgets::IntSliderOptions().DefaultValue(8192).Min(1).Max(8192).Tooltip( + "The chance for an enemy to be shiny is 1 / {Shiny Chance}")); +} + +static RegisterShipInitFunc initFunc(RegisterShiny); +static RegisterMenuInitFunc menuInitFunc(RegisterMenu); \ No newline at end of file diff --git a/soh/soh/Enhancements/Holiday/ProxySaw.cpp b/soh/soh/Enhancements/Holiday/ProxySaw.cpp new file mode 100644 index 00000000000..e5cbba5bbe9 --- /dev/null +++ b/soh/soh/Enhancements/Holiday/ProxySaw.cpp @@ -0,0 +1,545 @@ +#include "Holiday.hpp" +#include +#include "soh/SohGui/UIWidgets.hpp" +#include "soh/SohGui/ImGuiUtils.h" +#include "soh/Enhancements/game-interactor/GameInteractor.h" +#include "objects/object_dog/object_dog.h" +#include "soh/frame_interpolation.h" +#include "soh/Enhancements/randomizer/3drando/random.hpp" +#include "soh/Enhancements/randomizer/location_access.h" +#include "soh/Enhancements/randomizer/entrance.h" +#include +#include "soh_assets.h" +#include "objects/gameplay_field_keep/gameplay_field_keep.h" +#include "objects/object_md/object_md.h" +#include "src/overlays/actors/ovl_Door_Ana/z_door_ana.h" +extern "C" { +#include "macros.h" +#include "functions.h" +#include "variables.h" +#include "objects/object_wood02/object_wood02.h" +#include "scenes/overworld/spot00/spot00_room_0.h" +#include "scenes/overworld/spot04/spot04_room_0.h" +#include "scenes/overworld/spot04/spot04_room_1.h" +#include "scenes/overworld/spot20/spot20_room_0.h" +#include "scenes/overworld/spot03/spot03_room_0.h" +#include "scenes/overworld/spot15/spot15_room_0.h" + +void ResourceMgr_PatchGfxByName(const char* path, const char* patchName, int index, Gfx instruction); +void ResourceMgr_PatchCustomGfxByName(const char* path, const char* patchName, int index, Gfx instruction); +void ResourceMgr_UnpatchGfxByName(const char* path, const char* patchName); + +extern PlayState* gPlayState; +extern "C" s16 gEnSnowballId; +void DoorAna_SetupAction(DoorAna* doorAna, DoorAnaActionFunc actionFunc); +void DoorAna_GrabPlayer(DoorAna* doorAna, PlayState* play); +} + +#define CVAR(v) "gHoliday.Gameplay." v + +static CollisionPoly snowballPoly; +static Vec3f snowballPos; +static f32 raycastResult; + +static u32 iceBlockParams[] = { + 0x214, 0x1, 0x11, 0x10, 0x20, +}; + +static void SpawnSnowballs() { + if (gPlayState->sceneNum != SCENE_HYRULE_FIELD && gPlayState->sceneNum != SCENE_KAKARIKO_VILLAGE) { + return; + } + + int actorsSpawned = 0; + + while (actorsSpawned < 30) { + snowballPos.x = (float)(Random((gPlayState->sceneNum == SCENE_HYRULE_FIELD ? -10000 : -2700) + 10000, + (gPlayState->sceneNum == SCENE_HYRULE_FIELD ? 5000 : 2000) + 10000) - + (float)10000.0f); + snowballPos.y = 5000; + snowballPos.z = (float)(Random((gPlayState->sceneNum == SCENE_HYRULE_FIELD ? -1000 : -2000) + 10000, + (gPlayState->sceneNum == SCENE_HYRULE_FIELD ? 15000 : 2000) + 10000) - + (float)10000.0f); + + raycastResult = BgCheck_AnyRaycastFloor1(&gPlayState->colCtx, &snowballPoly, &snowballPos); + + if (raycastResult > BGCHECK_Y_MIN) { + Actor_Spawn(&gPlayState->actorCtx, gPlayState, gEnSnowballId, snowballPos.x, raycastResult, snowballPos.z, + 0, 0, 0, gPlayState->sceneNum == SCENE_HYRULE_FIELD, 0); + actorsSpawned++; + } + } +} + +static void SpawnIcebergs() { + if (gPlayState->sceneNum != SCENE_LAKE_HYLIA) { + return; + } + + int actorsSpawned = 0; + + Vec3f spawnedIceBlockPos[15]; + + while (actorsSpawned < 15) { + Vec3f iceBlockPos; + iceBlockPos.x = (float)(Random((-4200) + 10000, (3000) + 10000) - (float)10000.0f); + iceBlockPos.y = -1713.0f; + iceBlockPos.z = (float)(Random((2600) + 10000, (9000) + 10000) - (float)10000.0f); + + raycastResult = BgCheck_AnyRaycastFloor1(&gPlayState->colCtx, &snowballPoly, &iceBlockPos); + + if (raycastResult > BGCHECK_Y_MIN) { + + bool overlaps = false; + for (int i = 0; i < actorsSpawned; i++) { + if (Math_Vec3f_DistXZ(&spawnedIceBlockPos[i], &iceBlockPos) < 500.0f) { + overlaps = true; + break; + } + } + + if (overlaps) { + continue; + } + + if (LINK_IS_ADULT && !Flags_GetEventChkInf(EVENTCHKINF_RAISED_LAKE_HYLIA_WATER)) { + iceBlockPos.y = raycastResult; + } else { + iceBlockPos.y = -1310.0f; + } + + Actor_Spawn(&gPlayState->actorCtx, gPlayState, ACTOR_BG_SPOT08_ICEBLOCK, iceBlockPos.x, iceBlockPos.y, + iceBlockPos.z, 0, (s16)Random(0, 0xFFFF), 0, RandomElement(iceBlockParams), 0); + spawnedIceBlockPos[actorsSpawned] = iceBlockPos; + actorsSpawned++; + } + } +} + +std::vector validEntrances = { + ENTR_DEKU_TREE_ENTRANCE, + ENTR_KOKIRI_FOREST_OUTSIDE_DEKU_TREE, + ENTR_DODONGOS_CAVERN_ENTRANCE, + ENTR_DEATH_MOUNTAIN_TRAIL_OUTSIDE_DODONGOS_CAVERN, + ENTR_JABU_JABU_ENTRANCE, + ENTR_ZORAS_FOUNTAIN_OUTSIDE_JABU_JABU, + ENTR_FOREST_TEMPLE_ENTRANCE, + ENTR_SACRED_FOREST_MEADOW_OUTSIDE_TEMPLE, + ENTR_FIRE_TEMPLE_ENTRANCE, + ENTR_DEATH_MOUNTAIN_CRATER_OUTSIDE_TEMPLE, + ENTR_WATER_TEMPLE_ENTRANCE, + ENTR_LAKE_HYLIA_OUTSIDE_TEMPLE, + ENTR_SPIRIT_TEMPLE_ENTRANCE, + ENTR_DESERT_COLOSSUS_OUTSIDE_TEMPLE, + ENTR_SHADOW_TEMPLE_ENTRANCE, + ENTR_GRAVEYARD_OUTSIDE_TEMPLE, + ENTR_BOTTOM_OF_THE_WELL_ENTRANCE, + ENTR_KAKARIKO_VILLAGE_OUTSIDE_BOTTOM_OF_THE_WELL, + ENTR_ICE_CAVERN_ENTRANCE, + ENTR_ZORAS_FOUNTAIN_OUTSIDE_ICE_CAVERN, + ENTR_GERUDO_TRAINING_GROUND_ENTRANCE, + ENTR_GERUDOS_FORTRESS_OUTSIDE_GERUDO_TRAINING_GROUND, + ENTR_INSIDE_GANONS_CASTLE_ENTRANCE, + ENTR_CASTLE_GROUNDS_RAINBOW_BRIDGE_EXIT, + ENTR_MIDOS_HOUSE_0, + ENTR_KOKIRI_FOREST_OUTSIDE_MIDOS_HOUSE, + ENTR_SARIAS_HOUSE_0, + ENTR_KOKIRI_FOREST_OUTSIDE_SARIAS_HOUSE, + ENTR_TWINS_HOUSE_0, + ENTR_KOKIRI_FOREST_OUTSIDE_TWINS_HOUSE, + ENTR_KNOW_IT_ALL_BROS_HOUSE_0, + ENTR_KOKIRI_FOREST_OUTSIDE_KNOW_IT_ALL_HOUSE, + ENTR_KOKIRI_SHOP_0, + ENTR_KOKIRI_FOREST_OUTSIDE_SHOP, + ENTR_LAKESIDE_LABORATORY_0, + ENTR_LAKE_HYLIA_OUTSIDE_LAB, + ENTR_FISHING_POND_0, + ENTR_LAKE_HYLIA_OUTSIDE_FISHING_POND, + ENTR_CARPENTERS_TENT_0, + ENTR_GERUDO_VALLEY_OUTSIDE_TENT, + ENTR_MARKET_GUARD_HOUSE_0, + ENTR_MARKET_ENTRANCE_OUTSIDE_GUARD_HOUSE, + ENTR_HAPPY_MASK_SHOP_0, + ENTR_MARKET_DAY_OUTSIDE_HAPPY_MASK_SHOP, + ENTR_BOMBCHU_BOWLING_ALLEY_0, + ENTR_MARKET_DAY_OUTSIDE_BOMBCHU_BOWLING, + ENTR_POTION_SHOP_MARKET_0, + ENTR_MARKET_DAY_OUTSIDE_POTION_SHOP, + ENTR_TREASURE_BOX_SHOP_0, + ENTR_MARKET_DAY_OUTSIDE_TREASURE_BOX_SHOP, + ENTR_BOMBCHU_SHOP_1, + ENTR_BACK_ALLEY_DAY_OUTSIDE_BOMBCHU_SHOP, + ENTR_BACK_ALLEY_MAN_IN_GREEN_HOUSE, + ENTR_BACK_ALLEY_DAY_OUTSIDE_MAN_IN_GREEN_HOUSE, + ENTR_KAKARIKO_CENTER_GUEST_HOUSE_0, + ENTR_KAKARIKO_VILLAGE_OUTSIDE_CENTER_GUEST_HOUSE, + ENTR_HOUSE_OF_SKULLTULA_0, + ENTR_KAKARIKO_VILLAGE_OUTSIDE_SKULKLTULA_HOUSE, + ENTR_IMPAS_HOUSE_FRONT, + ENTR_KAKARIKO_VILLAGE_OUTSIDE_IMPAS_HOUSE_FRONT, + ENTR_IMPAS_HOUSE_BACK, + ENTR_KAKARIKO_VILLAGE_OUTSIDE_IMPAS_HOUSE_BACK, + ENTR_POTION_SHOP_GRANNY_0, + ENTR_KAKARIKO_VILLAGE_OUTSIDE_SHOP_GRANNY, + ENTR_GRAVEKEEPERS_HUT_0, + ENTR_GRAVEYARD_OUTSIDE_DAMPES_HUT, + ENTR_GORON_SHOP_0, + ENTR_GORON_CITY_OUTSIDE_SHOP, + ENTR_ZORA_SHOP_0, + ENTR_ZORAS_DOMAIN_OUTSIDE_SHOP, + ENTR_LON_LON_BUILDINGS_TALONS_HOUSE, + ENTR_LON_LON_RANCH_OUTSIDE_TALONS_HOUSE, + ENTR_STABLE_0, + ENTR_LON_LON_RANCH_OUTSIDE_STABLES, + ENTR_LON_LON_BUILDINGS_TOWER, + ENTR_LON_LON_RANCH_OUTSIDE_TOWER, + ENTR_BAZAAR_1, + ENTR_MARKET_DAY_OUTSIDE_BAZAAR, + ENTR_SHOOTING_GALLERY_1, + ENTR_MARKET_DAY_OUTSIDE_SHOOTING_GALLERY, + ENTR_BAZAAR_0, + ENTR_KAKARIKO_VILLAGE_OUTSIDE_BAZAAR, + ENTR_SHOOTING_GALLERY_0, + ENTR_KAKARIKO_VILLAGE_OUTSIDE_SHOOTING_GALLERY, + ENTR_GREAT_FAIRYS_FOUNTAIN_SPELLS_NAYRUS_COLOSSUS, + ENTR_DESERT_COLOSSUS_GREAT_FAIRY_EXIT, + ENTR_GREAT_FAIRYS_FOUNTAIN_SPELLS_DINS_HC, + ENTR_CASTLE_GROUNDS_GREAT_FAIRY_EXIT, + ENTR_GREAT_FAIRYS_FOUNTAIN_MAGIC_OGC_DD, + ENTR_POTION_SHOP_KAKARIKO_1, + ENTR_GREAT_FAIRYS_FOUNTAIN_MAGIC_DMC, + ENTR_DEATH_MOUNTAIN_CRATER_GREAT_FAIRY_EXIT, + ENTR_GREAT_FAIRYS_FOUNTAIN_MAGIC_DMT, + ENTR_DEATH_MOUNTAIN_TRAIL_GREAT_FAIRY_EXIT, + ENTR_GREAT_FAIRYS_FOUNTAIN_SPELLS_FARORES_ZF, + ENTR_ZORAS_FOUNTAIN_OUTSIDE_GREAT_FAIRY, + ENTR_LINKS_HOUSE_1, + ENTR_KOKIRI_FOREST_OUTSIDE_LINKS_HOUSE, + ENTR_TEMPLE_OF_TIME_ENTRANCE, + ENTR_TEMPLE_OF_TIME_EXTERIOR_DAY_OUTSIDE_TEMPLE, + ENTR_WINDMILL_AND_DAMPES_GRAVE_WINDMILL, + ENTR_KAKARIKO_VILLAGE_OUTSIDE_WINDMILL, + ENTR_POTION_SHOP_KAKARIKO_FRONT, + ENTR_KAKARIKO_VILLAGE_OUTSIDE_POTION_SHOP_FRONT, + ENTR_POTION_SHOP_KAKARIKO_BACK, + ENTR_KAKARIKO_VILLAGE_OUTSIDE_POTION_SHOP_BACK, + ENTR_GRAVE_WITH_FAIRYS_FOUNTAIN_0, + ENTR_GRAVEYARD_SHIELD_GRAVE_EXIT, + ENTR_REDEAD_GRAVE_0, + ENTR_GRAVEYARD_HEART_PIECE_GRAVE_EXIT, + ENTR_ROYAL_FAMILYS_TOMB_0, + ENTR_GRAVEYARD_ROYAL_TOMB_EXIT, + ENTR_WINDMILL_AND_DAMPES_GRAVE_GRAVE, + ENTR_GRAVEYARD_DAMPES_GRAVE_EXIT, + ENTR_LOST_WOODS_BRIDGE_EAST_EXIT, + ENTR_KOKIRI_FOREST_LOWER_EXIT, + ENTR_LOST_WOODS_SOUTH_EXIT, + ENTR_KOKIRI_FOREST_UPPER_EXIT, + ENTR_GORON_CITY_TUNNEL_SHORTCUT, + ENTR_LOST_WOODS_TUNNEL_SHORTCUT, + ENTR_ZORAS_RIVER_UNDERWATER_SHORTCUT, + ENTR_LOST_WOODS_UNDERWATER_SHORTCUT, + ENTR_SACRED_FOREST_MEADOW_SOUTH_EXIT, + ENTR_LOST_WOODS_NORTH_EXIT, + ENTR_HYRULE_FIELD_WOODED_EXIT, + ENTR_LOST_WOODS_BRIDGE_WEST_EXIT, + ENTR_LAKE_HYLIA_NORTH_EXIT, + ENTR_HYRULE_FIELD_FENCE_EXIT, + ENTR_GERUDO_VALLEY_EAST_EXIT, + ENTR_HYRULE_FIELD_ROCKY_PATH, + ENTR_MARKET_ENTRANCE_NEAR_GUARD_EXIT, + ENTR_HYRULE_FIELD_ON_BRIDGE_SPAWN, + ENTR_KAKARIKO_VILLAGE_FRONT_GATE, + ENTR_HYRULE_FIELD_STAIRS_EXIT, + ENTR_ZORAS_RIVER_WEST_EXIT, + ENTR_HYRULE_FIELD_RIVER_EXIT, + ENTR_LON_LON_RANCH_ENTRANCE, + ENTR_HYRULE_FIELD_CENTER_EXIT, + ENTR_ZORAS_DOMAIN_UNDERWATER_SHORTCUT, + ENTR_LAKE_HYLIA_UNDERWATER_SHORTCUT, + ENTR_GERUDOS_FORTRESS_EAST_EXIT, + ENTR_GERUDO_VALLEY_WEST_EXIT, + ENTR_HAUNTED_WASTELAND_EAST_EXIT, + ENTR_GERUDOS_FORTRESS_GATE_EXIT, + ENTR_DESERT_COLOSSUS_EAST_EXIT, + ENTR_HAUNTED_WASTELAND_WEST_EXIT, + ENTR_MARKET_SOUTH_EXIT, + ENTR_MARKET_ENTRANCE_NORTH_EXIT, + ENTR_CASTLE_GROUNDS_SOUTH_EXIT, + ENTR_MARKET_DAY_CASTLE_EXIT, + ENTR_TEMPLE_OF_TIME_EXTERIOR_DAY_GOSSIP_STONE_EXIT, + ENTR_MARKET_DAY_TEMPLE_EXIT, + ENTR_GRAVEYARD_ENTRANCE, + ENTR_KAKARIKO_VILLAGE_SOUTHEAST_EXIT, + ENTR_DEATH_MOUNTAIN_TRAIL_BOTTOM_EXIT, + ENTR_KAKARIKO_VILLAGE_GUARD_GATE, + ENTR_GORON_CITY_UPPER_EXIT, + ENTR_DEATH_MOUNTAIN_TRAIL_GC_EXIT, + ENTR_DEATH_MOUNTAIN_CRATER_GC_EXIT, + ENTR_GORON_CITY_DARUNIA_ROOM_EXIT, + ENTR_DEATH_MOUNTAIN_CRATER_UPPER_EXIT, + ENTR_DEATH_MOUNTAIN_TRAIL_SUMMIT_EXIT, + ENTR_ZORAS_DOMAIN_ENTRANCE, + ENTR_ZORAS_RIVER_WATERFALL_EXIT, + ENTR_ZORAS_FOUNTAIN_TUNNEL_EXIT, + ENTR_ZORAS_DOMAIN_KING_ZORA_EXIT, + ENTR_LAKE_HYLIA_RIVER_EXIT, + ENTR_HYRULE_FIELD_OWL_DROP, + ENTR_KAKARIKO_VILLAGE_OWL_DROP, + ENTR_LINKS_HOUSE_CHILD_SPAWN, + ENTR_HYRULE_FIELD_10, + ENTR_SACRED_FOREST_MEADOW_WARP_PAD, + ENTR_DEATH_MOUNTAIN_CRATER_WARP_PAD, + ENTR_LAKE_HYLIA_WARP_PAD, + ENTR_DESERT_COLOSSUS_WARP_PAD, + ENTR_GRAVEYARD_WARP_PAD, + ENTR_TEMPLE_OF_TIME_WARP_PAD, + ENTR_DEKU_TREE_BOSS_ENTRANCE, + ENTR_DEKU_TREE_BOSS_DOOR, + ENTR_DODONGOS_CAVERN_BOSS_ENTRANCE, + ENTR_DODONGOS_CAVERN_BOSS_DOOR, + ENTR_JABU_JABU_BOSS_ENTRANCE, + ENTR_JABU_JABU_BOSS_DOOR, + ENTR_FOREST_TEMPLE_BOSS_ENTRANCE, + ENTR_FOREST_TEMPLE_BOSS_DOOR, + ENTR_FIRE_TEMPLE_BOSS_ENTRANCE, + ENTR_FIRE_TEMPLE_BOSS_DOOR, + ENTR_WATER_TEMPLE_BOSS_ENTRANCE, + ENTR_WATER_TEMPLE_BOSS_DOOR, + ENTR_SPIRIT_TEMPLE_BOSS_ENTRANCE, + ENTR_SPIRIT_TEMPLE_BOSS_DOOR, + ENTR_SHADOW_TEMPLE_BOSS_ENTRANCE, + ENTR_SHADOW_TEMPLE_BOSS_DOOR, +}; + +static void RandomGrotto_WaitOpen(DoorAna* doorAna, PlayState* play) { + Actor* actor = &doorAna->actor; + Player* player = GET_PLAYER(play); + if (Math_StepToF(&actor->scale.x, 0.01f, 0.001f)) { + if ((actor->targetMode != 0) && (play->transitionTrigger == TRANS_TRIGGER_OFF) && + (player->stateFlags1 & PLAYER_STATE1_FLOOR_DISABLED) && (player->av1.actionVar1 == 0)) { + Random_Init(rand() % 0xFFFFFFFF); + play->nextEntranceIndex = RandomElement(validEntrances); + DoorAna_SetupAction((DoorAna*)actor, DoorAna_GrabPlayer); + } else { + if (!Player_InCsMode(play) && !(player->stateFlags1 & (PLAYER_STATE1_ON_HORSE | PLAYER_STATE1_IN_WATER)) && + actor->xzDistToPlayer <= 15.0f && -50.0f <= actor->yDistToPlayer && actor->yDistToPlayer <= 15.0f) { + player->stateFlags1 |= PLAYER_STATE1_FLOOR_DISABLED; + actor->targetMode = 1; + } else { + actor->targetMode = 0; + } + } + } + Actor_SetScale(actor, actor->scale.x); +} + +static void SpawnRandomGrotto() { + if (gPlayState->sceneNum == SCENE_TEMPLE_OF_TIME_EXTERIOR_DAY || + gPlayState->sceneNum == SCENE_TEMPLE_OF_TIME_EXTERIOR_NIGHT || + gPlayState->sceneNum == SCENE_TEMPLE_OF_TIME_EXTERIOR_RUINS) { + return; + } + + Vec3f pos; + pos.y = 9999.0f; + int spawnAttempts = 0; + while (spawnAttempts < 50) { + if (GET_PLAYER(gPlayState) != nullptr) { + pos.x = GET_PLAYER(gPlayState)->actor.world.pos.x; + pos.z = GET_PLAYER(gPlayState)->actor.world.pos.z; + } else { + pos.x = 0; + pos.z = 0; + } + // X/Z anywhere from -1000.0 to +1000.0 from player + pos.x += (float)(Random(0, 5000)) - 2500.0f; + pos.z += (float)(Random(0, 5000)) - 2500.0f; + + raycastResult = BgCheck_AnyRaycastFloor1(&gPlayState->colCtx, &snowballPoly, &pos); + + if (raycastResult > BGCHECK_Y_MIN) { + Actor* grotto = Actor_Spawn(&gPlayState->actorCtx, gPlayState, ACTOR_DOOR_ANA, pos.x, raycastResult, pos.z, + 0, 0, 0, 0, false); + DoorAna_SetupAction((DoorAna*)grotto, RandomGrotto_WaitOpen); + break; + } + + spawnAttempts++; + } +} + +static void OnConfigurationChanged() { + COND_HOOK(OnSceneSpawnActors, CVarGetInteger(CVAR("Snowballs"), 0), SpawnSnowballs); + COND_HOOK(OnPlayerUpdate, CVarGetInteger(CVAR("SuperBonk"), 0), []() { + Player* player = GET_PLAYER(gPlayState); + if (player->actor.bgCheckFlags & 0x08 && ABS(player->linearVelocity) > 15.0f) { + player->yaw = ((player->actor.wallYaw - player->yaw) + player->actor.wallYaw) - 0x8000; + Player_PlaySfx(&player->actor, NA_SE_PL_BODY_HIT); + } + }); + + COND_HOOK(OnPlayerBonk, CVarGetInteger(CVAR("SuperBonk"), 0), []() { + Player* player = GET_PLAYER(gPlayState); + + player->linearVelocity = -100.0f; + }); + COND_HOOK(OnSceneSpawnActors, CVarGetInteger(CVAR("Icebergs"), 0), SpawnIcebergs); + COND_HOOK(OnSceneSpawnActors, CVarGetInteger(CVAR("DownTheRabbitHole"), 0), SpawnRandomGrotto); +} + +static void RegisterMenu() { + WidgetPath path = { "Holiday", "Gameplay", SECTION_COLUMN_1 }; + + SohGui::mSohMenu->AddWidget(path, "Snowballs", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR("Snowballs")) + .Options( + UIWidgets::CheckboxOptions().Tooltip("Rogue snowballs will spawn in Hyrule Field and Kakariko Village.")); + + SohGui::mSohMenu->AddWidget(path, "Lake Hylia Icebergs", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR("Icebergs")) + .Options(UIWidgets::CheckboxOptions().Tooltip("Icebergs will spawn in Lake Hylia.")); + + SohGui::mSohMenu->AddWidget(path, "Down the Rabbit Hole", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR("DownTheRabbitHole")) + .Options(UIWidgets::CheckboxOptions().Tooltip( + "Random grottos will spawn throughout Hyrule. Who knows where they will take you?")); + + SohGui::mSohMenu->AddWidget(path, "Super Bonk", WIDGET_CVAR_CHECKBOX).CVar(CVAR("SuperBonk")); + + path.sidebarName = "Visual"; + path.column = SECTION_COLUMN_1; + + SohGui::mSohMenu->AddWidget(path, "Snow Everywhere/Decor", WIDGET_CVAR_CHECKBOX) + .CVar("gHoliday.Visual.SnowingWeather") + .Options(UIWidgets::CheckboxOptions().Tooltip( + "Enables the snow fall effect in all areas, colors trees and paths " + "white, and adds decor to Kak and Temple of Time. Best paired with the official holiday texture pack.")); + + SohGui::mSohMenu->AddWidget(path, "Festive Hats", WIDGET_CVAR_CHECKBOX) + .CVar("gHoliday.Visual.Hats") + .Options(UIWidgets::CheckboxOptions().Tooltip("Link and NPCs will wear festive holiday hats.")); + + SohGui::mSohMenu->AddWidget(path, "Present Chests", WIDGET_CVAR_CHECKBOX) + .CVar("gHoliday.Visual.PresentChests") + .Options(UIWidgets::CheckboxOptions().Tooltip("Treasure chests will use present textures.")); + SohGui::mSohMenu->AddWidget(path, "Ornament Triforce Pieces", WIDGET_CVAR_CHECKBOX) + .CVar("gHoliday.Visual.HolidayPieces") + .Options( + UIWidgets::CheckboxOptions().Tooltip("Replace Triforce pieces with festive holiday ornaments. *To see " + "changes in the item tracker, you must close and reopen the game")); + SohGui::mSohMenu->AddWidget(path, "Let It Snow", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_GENERAL("LetItSnow")) + .Options(UIWidgets::CheckboxOptions().Tooltip( + "Makes snow fall, changes chest texture colors to red and green, etc, for " + "December holidays.\nWill reset on restart outside of December 23-25.")); +} + +#define PATCH_GFX(path, name, cvar, index, instruction) \ + if (CVarGetInteger(cvar, 0)) { \ + ResourceMgr_PatchGfxByName(path, name, index, instruction); \ + } else { \ + ResourceMgr_UnpatchGfxByName(path, name); \ + } + +#define PATCH_TRIFORCE_WRAPPER(path, baseName, cvar, index, holidayDL) \ + do { \ + if (CVarGetInteger(cvar, 0)) { \ + ResourceMgr_PatchCustomGfxByName(path, baseName "Call", index, gsSPDisplayListOTRFilePath(holidayDL)); \ + ResourceMgr_PatchCustomGfxByName(path, baseName "End", (index) + 1, gsSPEndDisplayList()); \ + } else { \ + ResourceMgr_UnpatchGfxByName(path, baseName "Call"); \ + ResourceMgr_UnpatchGfxByName(path, baseName "End"); \ + } \ + } while (0) + +static void PatchTriforcePieces(void) { + const char* cvar = "gHoliday.Visual.HolidayPieces"; + + // index 0 is assumed to be safe adjust if needed after you inspect the DLs + PATCH_TRIFORCE_WRAPPER(gTriforcePiece0DL, "HolidayPiece0", cvar, 0, gHolidayPiece0DL); + PATCH_TRIFORCE_WRAPPER(gTriforcePiece1DL, "HolidayPiece1", cvar, 0, gHolidayPiece1DL); + PATCH_TRIFORCE_WRAPPER(gTriforcePiece2DL, "HolidayPiece2", cvar, 0, gHolidayPiece2DL); + PATCH_TRIFORCE_WRAPPER(gTriforcePieceCompletedDL, "HolidayPieceC", cvar, 0, gHolidayPieceCompletedDL); +} + +extern std::map triforcePieceMapping; + +static void Holiday_UpdateTriforcePieceTexture() { + auto it = triforcePieceMapping.find(RG_TRIFORCE_PIECE); + if (it == triforcePieceMapping.end()) { + return; + } + + if (CVarGetInteger("gHoliday.Visual.HolidayPieces", 0)) { + // Use ornament texture + it->second.texturePath = gHolidayPieceTex; + } else { + // Use normal Triforce texture + it->second.texturePath = gTriforcePieceTex; + } +} + +// Re-run whenever the CVar changes +static RegisterShipInitFunc sHolidayTriforcePieceTexture(Holiday_UpdateTriforcePieceTexture, + { "gHoliday.Visual.HolidayPieces" }); + +static void PatchTrees() { + PATCH_GFX(object_wood02_DL_007968, "Tree1", "gHoliday.Visual.SnowingWeather", 17, + gsDPSetPrimColor(0, 0, 255, 255, 255, 255)); + PATCH_GFX(object_wood02_DL_000090, "Tree2", "gHoliday.Visual.SnowingWeather", 17, + gsDPSetPrimColor(0, 0, 200, 255, 255, 255)); + PATCH_GFX(object_wood02_DL_000340, "Tree3", "gHoliday.Visual.SnowingWeather", 17, + gsDPSetPrimColor(0, 0, 255, 255, 255, 255)); + PATCH_GFX(object_wood02_DL_000340, "Tree4", "gHoliday.Visual.SnowingWeather", 24, + gsDPSetPrimColor(0, 0, 255, 255, 255, 255)); + PATCH_GFX(spot00_room_0DL_0139A8, "Path1", "gHoliday.Visual.SnowingWeather", 23, + gsDPSetPrimColor(0, 0, 100, 150, 255, 60)); + PATCH_GFX(spot00_room_0DL_013250, "Path2", "gHoliday.Visual.SnowingWeather", 23, + gsDPSetPrimColor(0, 0, 100, 150, 255, 60)); + PATCH_GFX(spot00_room_0DL_0143C8, "Path3", "gHoliday.Visual.SnowingWeather", 23, + gsDPSetPrimColor(0, 0, 100, 150, 255, 60)); + PATCH_GFX(spot04_room_0DL_018048, "Path4", "gHoliday.Visual.SnowingWeather", 24, + gsDPSetPrimColor(0, 0, 100, 150, 255, 60)); + PATCH_GFX(spot04_room_1DL_007810, "Path5", "gHoliday.Visual.SnowingWeather", 24, + gsDPSetPrimColor(0, 0, 100, 150, 255, 60)); + PATCH_GFX(spot20_room_0DL_0062D0, "Path6", "gHoliday.Visual.SnowingWeather", 23, + gsDPSetPrimColor(0, 0, 200, 230, 255, 30)); + PATCH_GFX(spot20_room_0DL_004460, "Path8", "gHoliday.Visual.SnowingWeather", 31, + gsDPSetPrimColor(0, 0, 200, 230, 255, 30)); + PATCH_GFX(spot20_room_0DL_004460, "Path9", "gHoliday.Visual.SnowingWeather", 118, + gsDPSetPrimColor(0, 0, 200, 230, 255, 30)); + PATCH_GFX(spot20_room_0DL_0065E8, "Path10", "gHoliday.Visual.SnowingWeather", 24, + gsDPSetPrimColor(0, 0, 200, 230, 255, 30)); + PATCH_GFX(spot03_room_0DL_00C4B0, "Path11", "gHoliday.Visual.SnowingWeather", 23, + gsDPSetPrimColor(0, 0, 200, 230, 255, 30)); + PATCH_GFX(spot15_room_0DL_00C748, "Path12", "gHoliday.Visual.SnowingWeather", 23, + gsDPSetPrimColor(0, 0, 200, 230, 255, 30)); + + static u32 blizzardActiveTimer = 0; + blizzardActiveTimer = 0; + CVarClear("gHoliday.Visual.SnowingWeatherActive"); + COND_HOOK(OnPlayerUpdate, CVarGetInteger("gHoliday.Visual.SnowingWeather", 0), []() { + // Every frame has a 1/1000 chance to start a blizzard if there isn't one already + if (blizzardActiveTimer == 0 && rand() % 1000 == 0) { + blizzardActiveTimer = 20 * 20; // Lasts for 20 seconds + CVarSetInteger("gHoliday.Visual.SnowingWeatherActive", 2); + } + if (blizzardActiveTimer > 0) { + blizzardActiveTimer--; + } + if (blizzardActiveTimer == 0) { + CVarClear("gHoliday.Visual.SnowingWeatherActive"); + } else if (blizzardActiveTimer < 20) { + CVarSetInteger("gHoliday.Visual.SnowingWeatherActive", 1); + } + }); +} + +static RegisterShipInitFunc initFuncTrees(PatchTrees, { "gHoliday.Visual.SnowingWeather" }); + +static RegisterShipInitFunc initFunc(OnConfigurationChanged, { CVAR("Snowballs"), CVAR("Icebergs"), + CVAR("DownTheRabbitHole"), CVAR("SuperBonk") }); +static RegisterMenuInitFunc menuInitFunc(RegisterMenu); + +static RegisterShipInitFunc sTriforcePiecesInit(PatchTriforcePieces, { "gHoliday.Visual.HolidayPieces" }); diff --git a/soh/soh/Enhancements/Holiday/Rando.cpp b/soh/soh/Enhancements/Holiday/Rando.cpp new file mode 100644 index 00000000000..3c9bc330c6f --- /dev/null +++ b/soh/soh/Enhancements/Holiday/Rando.cpp @@ -0,0 +1,41 @@ +#include "Holiday.hpp" +#include +#include "soh/SohGui/UIWidgets.hpp" +#include "soh/Enhancements/game-interactor/GameInteractor.h" +#include "soh/Enhancements/custom-message/CustomMessageManager.h" +#include "include/message_data_fmt.h" +#include "soh/OTRGlobals.h" + +extern "C" { +#include "macros.h" +#include "functions.h" +#include "variables.h" +extern PlayState* gPlayState; +} + +static void OnConfigurationChanged() { + COND_ID_HOOK(OnOpenText, 0x406B, IS_RANDO, [](u16* textId, bool* loadFromMessageTable) { + if (gPlayState->sceneNum != SCENE_KAKARIKO_VILLAGE) { + return; + } + + std::string message; + uint8_t current = gSaveContext.ship.quest.data.randomizer.triforcePiecesCollected; + uint8_t required = OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_TRIFORCE_HUNT_PIECES_REQUIRED); + + if (current < required) { + message = "The %yChristmas tree%w seems to be&missing some of %gits magic%w... Find all&ornaments to save " + "%rChristmas%w!"; + } else { + message = "The tree's magic has been fully&restored. %gMerry %rChristmas%w!"; + } + + auto messageEntry = CustomMessage(message); + messageEntry.Format(); + messageEntry.LoadIntoFont(); + *loadFromMessageTable = false; + }); + COND_HOOK(OnLoadGame, true, [](int16_t fileNum) { OnConfigurationChanged(); }); +} + +static RegisterShipInitFunc initFunc(OnConfigurationChanged); diff --git a/soh/soh/Enhancements/Holiday/aMannus.cpp b/soh/soh/Enhancements/Holiday/aMannus.cpp new file mode 100644 index 00000000000..52442b1d33d --- /dev/null +++ b/soh/soh/Enhancements/Holiday/aMannus.cpp @@ -0,0 +1,70 @@ +#include "Holiday.hpp" + +#define CVAR(v) "gHoliday.Gameplay." v + +extern "C" { +#include +#include "functions.h" +#include "variables.h" +#include "macros.h" +#include "objects/gameplay_keep/gameplay_keep.h" +extern PlayState* gPlayState; +} + +uint8_t rocsUseCount = 0; + +static void OnConfigurationChanged() { + + COND_HOOK(OnPlayerUpdate, CVarGetInteger(CVAR("RocsFeather"), 0), []() { + Player* player = GET_PLAYER(gPlayState); + // Reset Rocs count when touching the ground + if (player->actor.bgCheckFlags & 1) { + rocsUseCount = 0; + } + }); + + COND_VB_SHOULD(VB_USE_ITEM, CVarGetInteger(CVAR("RocsFeather"), 0), { + int32_t* usedItem = va_arg(args, int32_t*); + Player* player = GET_PLAYER(gPlayState); + + // Roc's Feather behaviour + if (*usedItem == ITEM_NAYRUS_LOVE) { + *should = false; + + if (!rocsUseCount) { + rocsUseCount++; + player->linearVelocity = 5.0f; + player->actor.velocity.y = 8.0f; + player->actor.world.rot.y = player->yaw = player->actor.shape.rot.y; + + func_80838940(player, (LinkAnimationHeader*)&gPlayerAnim_link_fighter_backturn_jump, + !(2 & 1) ? 5.8f : 3.5f, gPlayState, 0); + + Vec3f effectsPos = player->actor.home.pos; + effectsPos.y += 3; + f32 effectsScale = 1; + if (!gSaveContext.linkAge) { + effectsScale = 1.5f; + } + EffectSsGRipple_Spawn(gPlayState, &effectsPos, 200 * effectsScale, 300 * effectsScale, 1); + EffectSsGSplash_Spawn(gPlayState, &effectsPos, NULL, NULL, 0, 150 * effectsScale); + + player->stateFlags2 &= ~(PLAYER_STATE2_HOPPING); + + Player_PlaySfx(&player->actor, NA_SE_PL_SKIP); + } + } + }); +} + +static void RegisterMenu() { + WidgetPath path = { "Holiday", "Gameplay", SECTION_COLUMN_1 }; + + SohGui::mSohMenu->AddWidget(path, "Roc's Feather", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR("RocsFeather")) + .Options(UIWidgets::CheckboxOptions().Tooltip( + "Using Nayru's Love will now act as Roc's Feather instead! No magic required.")); +} + +static RegisterShipInitFunc initFunc(OnConfigurationChanged, { CVAR("RocsFeather") }); +static RegisterMenuInitFunc menuInitFunc(RegisterMenu); diff --git a/soh/soh/Enhancements/Holiday/lilDavid.cpp b/soh/soh/Enhancements/Holiday/lilDavid.cpp new file mode 100644 index 00000000000..8794fe03bfa --- /dev/null +++ b/soh/soh/Enhancements/Holiday/lilDavid.cpp @@ -0,0 +1,125 @@ +#include "Holiday.hpp" + +#include + +extern "C" { +#include "macros.h" +#include "functions.h" +#include "variables.h" +extern PlayState* gPlayState; +} + +#include "src/overlays/actors/ovl_En_Arrow/z_en_arrow.h" +#include "src/overlays/actors/ovl_En_Bom/z_en_bom.h" + +extern "C" { +void func_809B45E0(EnArrow*, PlayState*); +void func_809B4640(EnArrow*, PlayState*); +} + +#define CVAR(v) "gHoliday.Gameplay." v + +static void OnConfigurationChanged() { + if (!CVarGetInteger(CVAR("BombArrows.Enabled"), 0)) + CVarSetInteger(CVAR("BombArrows.Active"), 0); + + COND_HOOK(OnSaveFile, CVarGetInteger(CVAR("BombArrows.Enabled"), 0), [](int32_t file, int32_t sectionID) { + std::string cvar = StringHelper::Sprintf("%s%d", CVAR("BombArrows.Save"), file); + CVarSetInteger(cvar.c_str(), CVarGetInteger(CVAR("BombArrows.Active"), 0)); + Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); + }); + + COND_HOOK(OnLoadFile, CVarGetInteger(CVAR("BombArrows.Enabled"), 0), [](int32_t file) { + std::string cvar = StringHelper::Sprintf("%s%d", CVAR("BombArrows.Save"), file); + CVarSetInteger(CVAR("BombArrows.Active"), CVarGetInteger(cvar.c_str(), 0)); + }); + + COND_HOOK(OnCopyFile, CVarGetInteger(CVAR("BombArrows.Enabled"), 0), [](int32_t from, int32_t to) { + std::string cvarFrom = StringHelper::Sprintf("%s%d", CVAR("BombArrows.Save"), from); + std::string cvarTo = StringHelper::Sprintf("%s%d", CVAR("BombArrows.Save"), to); + CVarSetInteger(cvarTo.c_str(), CVarGetInteger(cvarFrom.c_str(), 0)); + Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); + }); + + COND_HOOK(OnDeleteFile, CVarGetInteger(CVAR("BombArrows.Enabled"), 0), [](int32_t file) { + std::string cvar = StringHelper::Sprintf("%s%d", CVAR("BombArrows.Save"), file); + CVarSetInteger(cvar.c_str(), 0); + Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); + }); + + COND_ID_HOOK(OnActorInit, ACTOR_EN_ARROW, CVarGetInteger(CVAR("BombArrows.Enabled"), 0), [](void* actorRef) { + EnArrow* arrow = (EnArrow*)actorRef; + if (!CVarGetInteger(CVAR("BombArrows.Active"), 0) || arrow->actor.params != ARROW_NORMAL || + AMMO(ITEM_BOMB) == 0 || gSaveContext.minigameState == 1 || gPlayState->shootingGalleryStatus > 1) + return; + + EnBom* bomb = (EnBom*)Actor_SpawnAsChild(&gPlayState->actorCtx, &arrow->actor, gPlayState, ACTOR_EN_BOM, + arrow->actor.world.pos.x, arrow->actor.world.pos.y, + arrow->actor.world.pos.z, 0, 0, 0, BOMB_BODY); + if (bomb == nullptr) + return; + + Actor_SetScale(&bomb->actor, 0.003f); + bomb->timer = 65; + }); + + COND_ID_HOOK(OnActorUpdate, ACTOR_EN_ARROW, CVarGetInteger(CVAR("BombArrows.Enabled"), 0), [](void* actorRef) { + EnArrow* arrow = (EnArrow*)actorRef; + if (!arrow->actor.child || arrow->actor.child->id != ACTOR_EN_BOM) + return; + + EnBom* bomb = (EnBom*)arrow->actor.child; + bomb->actor.world.pos = arrow->actor.world.pos; + f32 r = 8.0f; + f32 xrot = arrow->actor.world.rot.x; + f32 yrot = arrow->actor.world.rot.y; + bomb->actor.world.pos.x += r * Math_CosS(xrot) * Math_SinS(yrot); + bomb->actor.world.pos.y -= r * Math_SinS(xrot) + 2.0f; + bomb->actor.world.pos.z += r * Math_CosS(xrot) * Math_CosS(yrot); + + if (arrow->actor.parent == nullptr) { + if (bomb->timer > 60) { + Inventory_ChangeAmmo(ITEM_BOMB, -1); + } + bomb->timer = 52; + } else { + bomb->timer = 62; + } + + if (arrow->actionFunc == func_809B45E0 || arrow->actionFunc == func_809B4640 || + arrow->actor.params == ARROW_NORMAL_LIT) { + arrow->actor.child = nullptr; + bomb->actor.parent = nullptr; + bomb->timer = 2; + Actor_Kill(&arrow->actor); + } + }); + + COND_ID_HOOK(OnActorKill, ACTOR_EN_ARROW, CVarGetInteger(CVAR("BombArrows.Enabled"), 0), [](void* actorRef) { + EnArrow* arrow = (EnArrow*)actorRef; + if (!arrow->actor.child || arrow->actor.child->id != ACTOR_EN_BOM) + return; + Actor_Kill(arrow->actor.child); + }); + + COND_ID_HOOK(OnActorUpdate, ACTOR_EN_BOM, CVarGetInteger(CVAR("BombArrows.Enabled"), 0), [](void* actorRef) { + EnBom* bomb = (EnBom*)actorRef; + if (!bomb->actor.parent || bomb->actor.parent->id != ACTOR_EN_ARROW) + return; + + if (bomb->timer > 55 && bomb->timer < 60) + bomb->timer += 4; + if (bomb->timer > 45 && bomb->timer < 50) + bomb->timer += 4; + }); +} + +static void RegisterMenu() { + WidgetPath path = { "Holiday", "Gameplay", SECTION_COLUMN_1 }; + SohGui::mSohMenu->AddWidget(path, "Bomb Arrows", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR("BombArrows.Enabled")) + .Options(UIWidgets::CheckboxOptions().Tooltip("Equip bombs over an already equipped Bow to shoot bomb arrows")); +} + +static RegisterShipInitFunc initFunc(OnConfigurationChanged, { CVAR("BombArrows.Enabled") }); +static RegisterMenuInitFunc menuInitFunc(RegisterMenu); diff --git a/soh/soh/Enhancements/Presets/Presets.cpp b/soh/soh/Enhancements/Presets/Presets.cpp index c75303045de..4a601f6b76e 100644 --- a/soh/soh/Enhancements/Presets/Presets.cpp +++ b/soh/soh/Enhancements/Presets/Presets.cpp @@ -361,7 +361,7 @@ void DrawNewPresetPopup() { } void PresetsCustomWidget(WidgetInfo& info) { - ImGui::PushFont(OTRGlobals::Instance->fontMonoLargest); + ImGui::PushFont(OTRGlobals::Instance->fontMonoLarger); if (UIWidgets::Button("New Preset", UIWidgets::ButtonOptions( { { .disabled = (CVarGetInteger(CVAR_SETTING("DisableChanges"), 0) != 0), .disabledTooltip = "Disabled because of race lockout" } }) @@ -380,7 +380,7 @@ void PresetsCustomWidget(WidgetInfo& info) { bool hideBuiltIn = CVarGetInteger(CVAR_GENERAL("HideBuiltInPresets"), 0); UIWidgets::PushStyleTabs(THEME_COLOR); if (ImGui::BeginTable("PresetWidgetTable", PRESET_SECTION_MAX + 3)) { - ImGui::TableSetupColumn("Name", ImGuiTableColumnFlags_WidthFixed, 250); + ImGui::TableSetupColumn("Name", ImGuiTableColumnFlags_WidthFixed, 400); for (int i = PRESET_SECTION_SETTINGS; i < PRESET_SECTION_MAX; i++) { ImGui::TableSetupColumn(blockInfo[i].names[0].c_str()); } diff --git a/soh/soh/Enhancements/ResetHotKey.cpp b/soh/soh/Enhancements/ResetHotKey.cpp new file mode 100644 index 00000000000..104d729a23b --- /dev/null +++ b/soh/soh/Enhancements/ResetHotKey.cpp @@ -0,0 +1,98 @@ +#include +#include "soh/Enhancements/game-interactor/GameInteractor.h" +#include "soh/ShipInit.hpp" +#include "functions.h" +#include "soh/OTRGlobals.h" + +extern "C" { +#include "z64.h" +#include "overlays/gamestates/ovl_file_choose/file_choose.h" +} + +static constexpr int32_t CVAR_RESET_BTN_MASK_DEFAULT = BTN_CUSTOM_MODIFIER2; +#define CVAR_RESET_BTN_MASK_NAME "gSettings.ResetBtn" +#define CVAR_RESET_BTN_MASK_VALUE CVarGetInteger(CVAR_RESET_BTN_MASK_NAME, CVAR_RESET_BTN_MASK_DEFAULT) + +static constexpr int32_t CVAR_SETTINGS_MENU_BTN_MASK_DEFAULT = BTN_CUSTOM_MODIFIER1; +#define CVAR_SETTINGS_MENU_BTN_MASK_NAME "gSettings.SettingsMenuBtn" +#define CVAR_SETTINGS_MENU_BTN_MASK_VALUE \ + CVarGetInteger(CVAR_SETTINGS_MENU_BTN_MASK_NAME, CVAR_SETTINGS_MENU_BTN_MASK_DEFAULT) + +static constexpr int32_t CVAR_VOID_BTN_MASK_DEFAULT = 0; +#define CVAR_VOID_BTN_MASK_NAME "gDeveloperTools.VoidBtn" +#define CVAR_VOID_BTN_MASK_VALUE CVarGetInteger(CVAR_VOID_BTN_MASK_NAME, CVAR_VOID_BTN_MASK_DEFAULT) + +static void OnGameStateMainStartResetHotkey() { + const int32_t packed = CVarGetInteger("gSettings.ResetBtn", BTN_CUSTOM_MODIFIER2); + + const uint16_t mask = static_cast(packed & 0xFFFF); + + if (mask != 0 && CHECK_BTN_ANY(gGameState->input[0].press.button, mask) && + CHECK_BTN_ALL(gGameState->input[0].cur.button, mask)) { + + auto consoleWin = std::reinterpret_pointer_cast( + Ship::Context::GetInstance()->GetWindow()->GetGui()->GetGuiWindow("Console")); + + if (consoleWin) { + consoleWin->Dispatch("reset"); + } + } +} + +static void OnGameStateMainStartSettingsMenuHotkey() { + const int32_t packed = CVarGetInteger(CVAR_SETTINGS_MENU_BTN_MASK_NAME, CVAR_SETTINGS_MENU_BTN_MASK_DEFAULT); + const uint16_t mask = static_cast(packed & 0xFFFF); + + if (mask == 0) { + return; + } + + if (CHECK_BTN_ANY(gGameState->input[0].press.button, mask) && + CHECK_BTN_ALL(gGameState->input[0].cur.button, mask)) { + + auto gui = Ship::Context::GetInstance()->GetWindow()->GetGui(); + if (!gui) { + return; + } + + // Toggle the "menu" (the same thing Esc toggles), not the menubar. + auto menu = gui->GetMenu(); + if (menu) { + menu->ToggleVisibility(); + } + } +} + +static void OnGameStateMainStartVoidHotkey() { + const int32_t packed = CVarGetInteger("gDeveloperTools.VoidBtn", 0); + + const uint16_t mask = static_cast(packed & 0xFFFF); + + if (mask != 0 && CHECK_BTN_ANY(gGameState->input[0].press.button, mask) && + CHECK_BTN_ALL(gGameState->input[0].cur.button, mask)) { + + auto consoleWin = std::reinterpret_pointer_cast( + Ship::Context::GetInstance()->GetWindow()->GetGui()->GetGuiWindow("Console")); + + if (consoleWin) { + consoleWin->Dispatch("void"); + } + static HOOK_ID hookId = 0; + hookId = REGISTER_VB_SHOULD(VB_INFLICT_VOID_DAMAGE, { + *should = false; + GameInteractor::Instance->UnregisterGameHookForID(hookId); + }); + } +} + +static void RegisterResetHotkey() { + // Always available, all the time + COND_HOOK(OnGameStateMainStart, true, OnGameStateMainStartResetHotkey); + COND_HOOK(OnGameStateMainStart, true, OnGameStateMainStartSettingsMenuHotkey); + COND_HOOK(OnGameStateMainStart, true, OnGameStateMainStartVoidHotkey); +} + +// Re-register if the binding changes +static RegisterShipInitFunc initFuncResetHotkey(RegisterResetHotkey, + { CVAR_RESET_BTN_MASK_NAME, CVAR_SETTINGS_MENU_BTN_MASK_NAME, + CVAR_VOID_BTN_MASK_NAME }); diff --git a/soh/soh/Enhancements/RogueLike/ActorBehavior/ActorBehavior.cpp b/soh/soh/Enhancements/RogueLike/ActorBehavior/ActorBehavior.cpp new file mode 100644 index 00000000000..976b3263d00 --- /dev/null +++ b/soh/soh/Enhancements/RogueLike/ActorBehavior/ActorBehavior.cpp @@ -0,0 +1,115 @@ +#include "soh/Enhancements/RogueLike/RogueLike.h" +#include "soh/Enhancements/game-interactor/vanilla-behavior/GIVanillaBehavior.h" +#include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h" +#include "soh/ShipInit.hpp" +#include "soh/Notification/Notification.h" + +extern "C" { +#include "variables.h" + +// Quest Includes +#include "overlays/actors/ovl_En_Niw_Lady/z_en_niw_lady.h" +void func_80ABA778(EnNiwLady* thisx, PlayState* play); +} + +// This is kind of a catch-all for things that are simple enough to not need their own file. +static void MiscVanillaBehaviorHandler(GIVanillaBehavior id, bool* should, va_list originalArgs) { + va_list args; + va_copy(args, originalArgs); + + switch (id) { + case VB_GIVE_ITEM_MINUET_OF_FOREST: + case VB_GIVE_ITEM_BOLERO_OF_FIRE: + case VB_GIVE_ITEM_SERENADE_OF_WATER: + case VB_GIVE_ITEM_REQUIEM_OF_SPIRIT: + case VB_GIVE_ITEM_NOCTURNE_OF_SHADOW: + case VB_GIVE_ITEM_PRELUDE_OF_LIGHT: + case VB_GIVE_ITEM_ZELDAS_LULLABY: + case VB_GIVE_ITEM_EPONAS_SONG: + case VB_GIVE_ITEM_SARIAS_SONG: + case VB_GIVE_ITEM_SUNS_SONG: + case VB_GIVE_ITEM_SONG_OF_TIME: + case VB_GIVE_ITEM_SONG_OF_STORMS: + case VB_GIVE_ITEM_FROM_TARGET_IN_WOODS: + case VB_GIVE_ITEM_FROM_TALONS_CHICKENS: + case VB_GIVE_ITEM_FROM_DIVING_MINIGAME: + case VB_GIVE_ITEM_FROM_GORON: + case VB_GIVE_ITEM_FROM_LAB_DIVE: + case VB_GIVE_ITEM_FROM_SKULL_KID_SARIAS_SONG: + case VB_GIVE_ITEM_FROM_MAN_ON_ROOF: + case VB_GIVE_ITEM_FAIRY_OCARINA: + case VB_GIVE_ITEM_WEIRD_EGG: + case VB_GIVE_ITEM_STRENGTH_1: + case VB_GIVE_ITEM_ZELDAS_LETTER: + case VB_GIVE_ITEM_OCARINA_OF_TIME: + case VB_CHEST_USE_ICE_EFFECT: + *should = false; + break; + case VB_OPEN_KOKIRI_FOREST: { + *should = true; + break; + } + case VB_BE_ELIGIBLE_FOR_RAINBOW_BRIDGE: { + *should = true; + break; + } + case VB_GIVE_ITEM_LIGHT_ARROW: { + if (!gSaveContext.inventory.dungeonItems[SCENE_GANONS_TOWER]) { + Notification::Emit({ + .message = "You obtained Ganon's Boss Key!", + }); + gSaveContext.inventory.dungeonItems[SCENE_GANONS_TOWER] |= 1; + } + *should = false; + break; + } + case VB_BE_ELIGIBLE_FOR_LIGHT_ARROWS: { + *should = true; + + for (uint32_t reward : RogueLike::requiredRewards) { + if (!CHECK_QUEST_ITEM(reward)) { + *should = false; + } + } + + break; + } + case VB_GIVE_ITEM_FROM_ANJU_AS_ADULT: { + EnNiwLady* enNiwLady = va_arg(args, EnNiwLady*); + Flags_SetItemGetInf(ITEMGETINF_2C); + RogueLike::Quests::AddQuestById(RL_QUEST_KV_STALFOS); + enNiwLady->actionFunc = func_80ABA778; + *should = false; + break; + } + case VB_GIVE_ITEM_FROM_ANJU_AS_CHILD: { + Flags_SetItemGetInf(ITEMGETINF_0C); + *should = false; + break; + } + default: + break; + } +} + +static void OnEnemyDefeatHandler(void* actorRef) { + Actor* actor = static_cast(actorRef); + + switch (actor->id) { + default: + RogueLike::XP::SpawnXPGroup(actor->world.pos, CVarGetInteger("gRogueLike.XPDrop.Enemies", 50)); + break; + } +} + +static void InitActorBehavior() { + COND_HOOK(OnEnemyDefeat, IS_ROGUELIKE, OnEnemyDefeatHandler); + COND_HOOK(OnVanillaBehavior, IS_ROGUELIKE, MiscVanillaBehaviorHandler); + + COND_ID_HOOK(ShouldActorInit, ACTOR_DEMO_KEKKAI, IS_ROGUELIKE, [](void* actorRef, bool* should) { + // Prevent the barrier from initializing in Roguelike mode + *should = false; + }); +} + +static RegisterShipInitFunc initFunc(InitActorBehavior, { "IS_ROGUELIKE" }); \ No newline at end of file diff --git a/soh/soh/Enhancements/RogueLike/ActorBehavior/BusinessScrubs.cpp b/soh/soh/Enhancements/RogueLike/ActorBehavior/BusinessScrubs.cpp new file mode 100644 index 00000000000..7513fca2d0d --- /dev/null +++ b/soh/soh/Enhancements/RogueLike/ActorBehavior/BusinessScrubs.cpp @@ -0,0 +1,39 @@ +#include "soh/Enhancements/RogueLike/RogueLike.h" +#include "soh/Enhancements/game-interactor/vanilla-behavior/GIVanillaBehavior.h" +#include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h" +#include "soh/ObjectExtension/ActorListIndex.h" +#include "soh/ShipInit.hpp" + +extern "C" { +#include "variables.h" +#include "overlays/actors/ovl_En_Dns/z_en_dns.h" + +extern PlayState* gPlayState; +} + +std::set> killedScrubs; + +static void InitBusinessScrubsBehavior() { + killedScrubs.clear(); + + COND_ID_HOOK(ShouldActorInit, ACTOR_EN_DNS, IS_ROGUELIKE, [](void* actor, bool* should) { + EnDns* scrubActor = static_cast(actor); + int16_t actorIndex = GetActorListIndex((Actor*)scrubActor); + + *should = false; + + if (actorIndex == -1) { + actorIndex = scrubActor->actor.home.pos.x + scrubActor->actor.home.pos.z; + } + + auto tupleKey = std::make_tuple(gPlayState->sceneNum, gPlayState->roomCtx.curRoom.num, actorIndex); + + if (killedScrubs.find(tupleKey) == killedScrubs.end()) { + RogueLike::XP::SpawnXPGroup(scrubActor->actor.world.pos, + CVarGetInteger("gRogueLike.XPDrop.BusinessScrubs", 100)); + killedScrubs.insert(tupleKey); + } + }); +} + +static RegisterShipInitFunc initFunc(InitBusinessScrubsBehavior, { "IS_ROGUELIKE" }); diff --git a/soh/soh/Enhancements/RogueLike/ActorBehavior/Chests.cpp b/soh/soh/Enhancements/RogueLike/ActorBehavior/Chests.cpp new file mode 100644 index 00000000000..2b009831a90 --- /dev/null +++ b/soh/soh/Enhancements/RogueLike/ActorBehavior/Chests.cpp @@ -0,0 +1,57 @@ +#include "soh/Enhancements/RogueLike/RogueLike.h" +#include "soh/Enhancements/game-interactor/vanilla-behavior/GIVanillaBehavior.h" +#include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h" +#include "soh/ObjectExtension/ActorListIndex.h" +#include "soh/ShipInit.hpp" + +extern "C" { +#include "variables.h" +#include "src/overlays/actors/ovl_En_Box/z_en_box.h" + +s32 Player_SetupWaitForPutAway(PlayState* play, Player* player, AfterPutAwayFunc afterPutAwayFunc); +void Player_SetupActionPreserveAnimMovement(PlayState* play, Player* player, PlayerActionFunc actionFunc, s32 flags); +void func_8084DFAC(PlayState* play, Player* player); +} + +void Player_Action_8084E6D4_overridden(Player* player, PlayState* play) { + if (LinkAnimation_Update(play, &player->skelAnime)) { + // Player_StopCutscene(player); ?? + func_8084DFAC(play, player); + + EnBox* enBox = (EnBox*)player->interactRangeActor; + + RogueLike::XP::SpawnXPGroup(enBox->dyna.actor.world.pos, CVarGetInteger("gRogueLike.XPDrop.Chests", 50)); + + Sfx_PlaySfxCentered(NA_SE_SY_GET_RUPY); + } +} + +void func_8083A434_overridden(PlayState* play, Player* player) { + Player_SetupActionPreserveAnimMovement(play, player, Player_Action_8084E6D4_overridden, 0); + player->stateFlags1 |= PLAYER_STATE1_GETTING_ITEM | PLAYER_STATE1_IN_CUTSCENE; +} + +// This simply prevents the player from getting an item from the chest, but still +// plays the chest opening animation and ensure the treasure chest flag is set +static void InitChestsBehavior() { + COND_VB_SHOULD(VB_GIVE_ITEM_FROM_CHEST, IS_ROGUELIKE, { + EnBox* enBox = va_arg(args, EnBox*); + Actor* actor = (Actor*)enBox; + Player* player = GET_PLAYER(gPlayState); + + auto getItemId = actor->params >> 5 & 0x7F; + if (getItemId == GI_KEY_BOSS || getItemId == GI_KEY_SMALL) { + return; + } + + Player_SetupWaitForPutAway(gPlayState, player, func_8083A434_overridden); + *should = false; + }); + + // Replace the item in the chest with a recovery heart, to prevent any other item side effects + // COND_ID_HOOK(ShouldActorInit, ACTOR_EN_BOX, IS_ROGUELIKE, [](Actor* actor, bool* should) { + // actor->params = ((actor->params & ~(0x7F << 5)) | ((GI_HEART & 0x7F) << 5)); + // }); +} + +static RegisterShipInitFunc initFunc(InitChestsBehavior, { "IS_ROGUELIKE" }); diff --git a/soh/soh/Enhancements/RogueLike/ActorBehavior/Enemies.cpp b/soh/soh/Enhancements/RogueLike/ActorBehavior/Enemies.cpp new file mode 100644 index 00000000000..f488bf39229 --- /dev/null +++ b/soh/soh/Enhancements/RogueLike/ActorBehavior/Enemies.cpp @@ -0,0 +1,59 @@ +#include "soh/Enhancements/RogueLike/RogueLike.h" +#include "soh/Enhancements/game-interactor/vanilla-behavior/GIVanillaBehavior.h" +#include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h" +#include "soh/ObjectExtension/ActorListIndex.h" +#include "soh/ShipInit.hpp" + +extern "C" { +#include "variables.h" + +extern PlayState* gPlayState; +} + +#define ENEMY_PLATE_MAX CVarGetInteger("gRogueLike.EnemyPlateMax", 3) +#define PLATE_CHANCE CVarGetInteger("gRogueLike.EnemyPlateChance", 25) + +uint16_t plateChanceRoll = -1; +std::vector platedEnemies; + +static void InitEnemyBehavior() { + COND_HOOK(OnActorInit, IS_ROGUELIKE, [](void* actor) { + Actor* refActor = static_cast(actor); + + if (refActor->category != ACTORCAT_ENEMY) { + return; + } + + if (platedEnemies.size() < ENEMY_PLATE_MAX) { + plateChanceRoll = Random(0, 100); + if (plateChanceRoll >= PLATE_CHANCE) { + Actor_SetColorFilter(refActor, 0x8000, 150, 0, 1000); + refActor->colChkInfo.health *= 5; + platedEnemies.push_back(refActor); + } + } + }); + + COND_HOOK(OnActorUpdate, IS_ROGUELIKE, [](void* actor) { + Actor* refActor = static_cast(actor); + if (refActor->id == ACTOR_OBJ_TSUBO && refActor->params == 256) { + Actor_SetColorFilter(refActor, 0x1000, 150, 0, 1000); + return; + } + + if (refActor->category != ACTORCAT_ENEMY) { + return; + } + + for (auto& enemy : platedEnemies) { + if (enemy == refActor) { + Actor_SetColorFilter(refActor, 0x8000, 150, 0, 1000); + break; + } + } + }); + + COND_HOOK(OnSceneInit, IS_ROGUELIKE, [](u16 sceneNum) { platedEnemies.clear(); }); +} + +static RegisterShipInitFunc initFunc(InitEnemyBehavior, { "IS_ROGUELIKE" }); diff --git a/soh/soh/Enhancements/RogueLike/ActorBehavior/FreestandingItems.cpp b/soh/soh/Enhancements/RogueLike/ActorBehavior/FreestandingItems.cpp new file mode 100644 index 00000000000..cadb2efaf82 --- /dev/null +++ b/soh/soh/Enhancements/RogueLike/ActorBehavior/FreestandingItems.cpp @@ -0,0 +1,72 @@ +#include "soh/Enhancements/RogueLike/RogueLike.h" +#include "soh/Enhancements/game-interactor/vanilla-behavior/GIVanillaBehavior.h" +#include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h" +#include "soh/ObjectExtension/ActorListIndex.h" +#include "soh/ShipInit.hpp" +#include "soh/Enhancements/custom-item/CustomItem.h" + +extern "C" { +#include "variables.h" + +extern PlayState* gPlayState; +} + +std::set> collectedItems; + +static void InitFreestandingItemsBehavior() { + collectedItems.clear(); + + COND_ID_HOOK(ShouldActorInit, ACTOR_EN_ITEM00, IS_ROGUELIKE, [](void* actor, bool* should) { + EnItem00* enItem00 = static_cast(actor); + + if (enItem00->actor.params == ITEM00_NONE) { + return; + } + + int xpAmount = 0; + switch (enItem00->actor.params) { + case ITEM00_RUPEE_GREEN: + xpAmount = 1; + break; + case ITEM00_RUPEE_BLUE: + xpAmount = 5; + break; + case ITEM00_RUPEE_RED: + xpAmount = 20; + break; + case ITEM00_RUPEE_PURPLE: + xpAmount = 50; + break; + case ITEM00_HEART: + xpAmount = 20; + break; + case ITEM00_HEART_PIECE: + xpAmount = 100; + break; + case ITEM00_HEART_CONTAINER: + xpAmount = 200; + break; + default: + xpAmount = 10; + break; + } + + int16_t actorIndex = GetActorListIndex((Actor*)enItem00); + + *should = false; + + if (actorIndex == -1) { + actorIndex = enItem00->actor.home.pos.x + enItem00->actor.home.pos.z; + } + + auto tupleKey = std::make_tuple(gPlayState->sceneNum, gPlayState->roomCtx.curRoom.num, actorIndex); + + if (collectedItems.find(tupleKey) == collectedItems.end()) { + RogueLike::XP::SpawnXPOrb(enItem00->actor.world.pos, xpAmount, + CustomItem::STOP_BOBBING | CustomItem::ENABLE_GRAVITY); + collectedItems.insert(tupleKey); + } + }); +} + +static RegisterShipInitFunc initFunc(InitFreestandingItemsBehavior, { "IS_ROGUELIKE" }); diff --git a/soh/soh/Enhancements/RogueLike/ActorBehavior/Grass.cpp b/soh/soh/Enhancements/RogueLike/ActorBehavior/Grass.cpp new file mode 100644 index 00000000000..b3da3d56d74 --- /dev/null +++ b/soh/soh/Enhancements/RogueLike/ActorBehavior/Grass.cpp @@ -0,0 +1,38 @@ +#include "soh/Enhancements/RogueLike/RogueLike.h" +#include "soh/Enhancements/game-interactor/vanilla-behavior/GIVanillaBehavior.h" +#include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h" +#include "soh/ObjectExtension/ActorListIndex.h" +#include "soh/ShipInit.hpp" + +extern "C" { +#include "variables.h" +#include "overlays/actors/ovl_En_Kusa/z_en_kusa.h" + +extern PlayState* gPlayState; +} + +std::set> brokenGrass; + +static void InitGrassBehavior() { + brokenGrass.clear(); + + COND_VB_SHOULD(VB_GRASS_DROP_ITEM, IS_ROGUELIKE, { + EnKusa* grassActor = va_arg(args, EnKusa*); + int16_t actorIndex = GetActorListIndex((Actor*)grassActor); + + *should = false; + + if (actorIndex == -1) { + actorIndex = grassActor->actor.home.pos.x + grassActor->actor.home.pos.z; + } + + auto tupleKey = std::make_tuple(gPlayState->sceneNum, gPlayState->roomCtx.curRoom.num, actorIndex); + + if (brokenGrass.find(tupleKey) == brokenGrass.end()) { + RogueLike::XP::SpawnXPGroup(grassActor->actor.world.pos, CVarGetInteger("gRogueLike.XPDrop.Grass", 20)); + brokenGrass.insert(tupleKey); + } + }); +} + +static RegisterShipInitFunc initFunc(InitGrassBehavior, { "IS_ROGUELIKE" }); diff --git a/soh/soh/Enhancements/RogueLike/ActorBehavior/Pots.cpp b/soh/soh/Enhancements/RogueLike/ActorBehavior/Pots.cpp new file mode 100644 index 00000000000..c99fd362567 --- /dev/null +++ b/soh/soh/Enhancements/RogueLike/ActorBehavior/Pots.cpp @@ -0,0 +1,34 @@ +#include "soh/Enhancements/RogueLike/RogueLike.h" +#include "soh/Enhancements/game-interactor/vanilla-behavior/GIVanillaBehavior.h" +#include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h" +#include "soh/ObjectExtension/ActorListIndex.h" +#include "soh/ShipInit.hpp" + +extern "C" { +#include "variables.h" +#include "overlays/actors/ovl_Obj_Tsubo/z_obj_tsubo.h" + +extern PlayState* gPlayState; +} + +std::set> brokenPots; + +static void InitPotsBehavior() { + brokenPots.clear(); + + COND_VB_SHOULD(VB_POT_DROP_ITEM, IS_ROGUELIKE, { + ObjTsubo* potActor = va_arg(args, ObjTsubo*); + int16_t actorIndex = GetActorListIndex((Actor*)potActor); + + *should = false; + + auto tupleKey = std::make_tuple(gPlayState->sceneNum, gPlayState->roomCtx.curRoom.num, actorIndex); + + if (brokenPots.find(tupleKey) == brokenPots.end()) { + RogueLike::XP::SpawnXPGroup(potActor->actor.world.pos, CVarGetInteger("gRogueLike.XPDrop.Pots", 20)); + brokenPots.insert(tupleKey); + } + }); +} + +static RegisterShipInitFunc initFunc(InitPotsBehavior, { "IS_ROGUELIKE" }); diff --git a/soh/soh/Enhancements/RogueLike/ActorBehavior/Rocks.cpp b/soh/soh/Enhancements/RogueLike/ActorBehavior/Rocks.cpp new file mode 100644 index 00000000000..fdce063c7cb --- /dev/null +++ b/soh/soh/Enhancements/RogueLike/ActorBehavior/Rocks.cpp @@ -0,0 +1,38 @@ +#include "soh/Enhancements/RogueLike/RogueLike.h" +#include "soh/Enhancements/game-interactor/vanilla-behavior/GIVanillaBehavior.h" +#include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h" +#include "soh/ObjectExtension/ActorListIndex.h" +#include "soh/ShipInit.hpp" + +extern "C" { +#include "variables.h" +#include "overlays/actors/ovl_En_Ishi/z_en_ishi.h" + +extern PlayState* gPlayState; +} + +std::set> brokenRocks; + +static void InitRocksBehavior() { + brokenRocks.clear(); + + COND_VB_SHOULD(VB_ROCK_DROP_ITEM, IS_ROGUELIKE, { + EnIshi* rockActor = va_arg(args, EnIshi*); + int16_t actorIndex = GetActorListIndex((Actor*)rockActor); + + *should = false; + + if (actorIndex == -1) { + actorIndex = rockActor->actor.home.pos.x + rockActor->actor.home.pos.z; + } + + auto tupleKey = std::make_tuple(gPlayState->sceneNum, gPlayState->roomCtx.curRoom.num, actorIndex); + + if (brokenRocks.find(tupleKey) == brokenRocks.end()) { + RogueLike::XP::SpawnXPGroup(rockActor->actor.world.pos, CVarGetInteger("gRogueLike.XPDrop.Rocks", 20)); + brokenRocks.insert(tupleKey); + } + }); +} + +static RegisterShipInitFunc initFunc(InitRocksBehavior, { "IS_ROGUELIKE" }); diff --git a/soh/soh/Enhancements/RogueLike/ActorBehavior/Trees.cpp b/soh/soh/Enhancements/RogueLike/ActorBehavior/Trees.cpp new file mode 100644 index 00000000000..3f831d378ed --- /dev/null +++ b/soh/soh/Enhancements/RogueLike/ActorBehavior/Trees.cpp @@ -0,0 +1,38 @@ +#include "soh/Enhancements/RogueLike/RogueLike.h" +#include "soh/Enhancements/game-interactor/vanilla-behavior/GIVanillaBehavior.h" +#include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h" +#include "soh/ObjectExtension/ActorListIndex.h" +#include "soh/ShipInit.hpp" + +extern "C" { +#include "variables.h" +#include "overlays/actors/ovl_En_Wood02/z_en_wood02.h" + +extern PlayState* gPlayState; +} + +std::set> bonkedTrees; + +static void InitTreesBehavior() { + bonkedTrees.clear(); + + COND_VB_SHOULD(VB_TREE_DROP_ITEM, IS_ROGUELIKE, { + EnWood02* treeActor = va_arg(args, EnWood02*); + int16_t actorIndex = GetActorListIndex((Actor*)treeActor); + + *should = false; + + if (actorIndex == -1) { + actorIndex = treeActor->actor.home.pos.x + treeActor->actor.home.pos.z; + } + + auto tupleKey = std::make_tuple(gPlayState->sceneNum, gPlayState->roomCtx.curRoom.num, actorIndex); + + if (bonkedTrees.find(tupleKey) == bonkedTrees.end()) { + RogueLike::XP::SpawnXPGroup(treeActor->actor.world.pos, CVarGetInteger("gRogueLike.XPDrop.Trees", 20)); + bonkedTrees.insert(tupleKey); + } + }); +} + +static RegisterShipInitFunc initFunc(InitTreesBehavior, { "IS_ROGUELIKE" }); diff --git a/soh/soh/Enhancements/RogueLike/Choices.hpp b/soh/soh/Enhancements/RogueLike/Choices.hpp new file mode 100644 index 00000000000..13201aa19a4 --- /dev/null +++ b/soh/soh/Enhancements/RogueLike/Choices.hpp @@ -0,0 +1,238 @@ +#ifndef ROGUELIKE_CHOICES_HPP +#define ROGUELIKE_CHOICES_HPP + +#include "soh/Enhancements/randomizer/3drando/random.hpp" +#include +#include + +extern "C" { +#include "variables.h" +#include "macros.h" +#include "functions.h" + +extern PlayState* gPlayState; +} + +namespace RogueLike { + +namespace Choices { + +typedef struct ChoiceCard { + std::string textureName; + std::string name; + uint32_t value; + std::function canSelect; + std::function onSelect; +} ChoiceCard; + +inline bool CanAlwaysSelect(uint32_t value) { + return true; +} + +inline void OnSelectLocation(uint32_t value) { + gSaveContext.cutsceneIndex = 0; + gSaveContext.entranceIndex = value; +} + +inline void Noop(uint32_t value) { +} + +#define LOCATION_CHOICE(texture, name, entrance) \ + { #texture, name, entrance, CanAlwaysSelect, OnSelectLocation } + +inline std::vector Locations = { + LOCATION_CHOICE(ITEM_KOKIRI_EMERALD, "Kokiri Forest", ENTR_LINKS_HOUSE_CHILD_SPAWN), + LOCATION_CHOICE(ITEM_GORON_RUBY, "Goron City", ENTR_GORON_CITY_UPPER_EXIT), + LOCATION_CHOICE(ITEM_SCALE_SILVER, "Lake Hylia", ENTR_LAKE_HYLIA_WARP_PAD), + LOCATION_CHOICE(ITEM_ZORA_SAPPHIRE, "Zora's Domain", ENTR_ZORAS_DOMAIN_ENTRANCE), + LOCATION_CHOICE(ITEM_MEDALLION_SPIRIT, "Desert Colossus", ENTR_DESERT_COLOSSUS_WARP_PAD), + LOCATION_CHOICE(ITEM_SKULL_TOKEN, "Ganon's Castle", ENTR_GANONS_TOWER_0) +}; + +inline bool CanSelectSong(uint32_t value) { + return !CHECK_QUEST_ITEM(value - ITEM_SONG_MINUET + QUEST_SONG_MINUET); +} + +inline void GiveItem(uint32_t value) { + Item_Give(gPlayState, value); +} + +inline bool IsItemInSlot(uint32_t value) { + return gSaveContext.inventory.items[SLOT(value)] == ITEM_NONE; +} + +inline void UpgradeMagic(uint32_t value) { + if (value == ITEM_MAGIC_SMALL) { + gSaveContext.isMagicAcquired = true; + gSaveContext.magicFillTarget = MAGIC_NORMAL_METER; + Magic_Fill(gPlayState); + } else if (value == ITEM_MAGIC_LARGE) { + if (!gSaveContext.isMagicAcquired) { + gSaveContext.isMagicAcquired = true; + } + gSaveContext.isDoubleMagicAcquired = true; + gSaveContext.magicFillTarget = MAGIC_DOUBLE_METER; + gSaveContext.magicLevel = 0; + Magic_Fill(gPlayState); + } +} + +#define SONG_CHOICE(song, name) \ + { "QUEST_SONG_" #song, name, ITEM_SONG_##song, CanSelectSong, GiveItem } + +inline std::vector Songs = { + SONG_CHOICE(MINUET, "Minuet of Forest"), SONG_CHOICE(BOLERO, "Bolero of Fire"), + SONG_CHOICE(SERENADE, "Serenade of Water"), SONG_CHOICE(REQUIEM, "Requiem of Spirit"), + SONG_CHOICE(NOCTURNE, "Nocturne of Shadow"), SONG_CHOICE(PRELUDE, "Prelude of Light"), + SONG_CHOICE(LULLABY, "Zelda's Lullaby"), SONG_CHOICE(EPONA, "Epona's Song"), + SONG_CHOICE(SARIA, "Saria's Song"), SONG_CHOICE(SUN, "Sun's Song"), + SONG_CHOICE(TIME, "Song of Time"), SONG_CHOICE(STORMS, "Song of Storms"), +}; + +#define ITEM_CHOICE(item, name) \ + { #item, name, item, IsItemInSlot, GiveItem } + +inline std::vector Items = { + ITEM_CHOICE(ITEM_STICK, "Deku Stick"), + ITEM_CHOICE(ITEM_NUT, "Deku Nut"), + ITEM_CHOICE(ITEM_BOW, "Bow"), + ITEM_CHOICE(ITEM_SLINGSHOT, "Slingshot"), + ITEM_CHOICE(ITEM_BOTTLE, "Bottle"), + { "ITEM_BOMB", "Bombs", ITEM_BOMB_BAG_20, + [](int32_t _) { return gSaveContext.inventory.items[SLOT(ITEM_BOMB)] == ITEM_NONE; }, GiveItem }, + { "ITEM_BOMBCHU", "Bombchu", ITEM_BOMBCHUS_20, + [](int32_t _) { + return gSaveContext.inventory.items[SLOT(ITEM_BOMB)] != ITEM_NONE && IsItemInSlot(ITEM_BOMBCHU); + }, + GiveItem }, + ITEM_CHOICE(ITEM_HOOKSHOT, "Hookshot"), + // Longshot requires hookshot + { "ITEM_LONGSHOT", "Longshot", ITEM_LONGSHOT, + [](int32_t _) { return gSaveContext.inventory.items[SLOT(ITEM_LONGSHOT)] == ITEM_HOOKSHOT; }, GiveItem }, + ITEM_CHOICE(ITEM_MASK_BUNNY, "Bunny Hood"), + ITEM_CHOICE(ITEM_OCARINA_FAIRY, "Fairy Ocarina"), + ITEM_CHOICE(ITEM_ARROW_FIRE, "Fire Arrow"), + ITEM_CHOICE(ITEM_ARROW_ICE, "Ice Arrow"), + ITEM_CHOICE(ITEM_ARROW_LIGHT, "Light Arrow"), + ITEM_CHOICE(ITEM_BOOMERANG, "Boomerang"), + ITEM_CHOICE(ITEM_HAMMER, "Megaton Hammer"), + ITEM_CHOICE(ITEM_LENS, "Lens of Truth"), + ITEM_CHOICE(ITEM_DINS_FIRE, "Din's Fire"), + ITEM_CHOICE(ITEM_FARORES_WIND, "Farore's Wind"), + ITEM_CHOICE(ITEM_NAYRUS_LOVE, "Nayru's Love"), +}; + +inline bool IsRLStatUnder100(uint32_t value) { + return gSaveContext.ship.quest.data.rogueLike.stats[value] < 100; +} + +inline void IncreaseRLStat(uint32_t value) { + gSaveContext.ship.quest.data.rogueLike.stats[value]++; +} + +#define STAT_CHOICE(texture, stat, name) \ + { #texture, name, stat, IsRLStatUnder100, IncreaseRLStat } + +inline std::vector Stats = { + STAT_CHOICE(ITEM_SWORD_KOKIRI, RL_ATTACK, "Attack"), + STAT_CHOICE(ITEM_SHIELD_DEKU, RL_DEFENSE, "Defense"), + STAT_CHOICE(ITEM_MASK_BUNNY, RL_SPEED, "Speed"), +}; + +#define EQUIP_CHOICE(item, name, equipType, equipInv) \ + { \ +#item, name, item, [](int32_t _) { return !CHECK_OWNED_EQUIP(equipType, equipInv); }, GiveItem \ + } + +inline std::vector Equipment = { + EQUIP_CHOICE(ITEM_SWORD_KOKIRI, "Kokiri Sword", EQUIP_TYPE_SWORD, EQUIP_INV_SWORD_KOKIRI), + EQUIP_CHOICE(ITEM_SWORD_MASTER, "Master Sword", EQUIP_TYPE_SWORD, EQUIP_INV_SWORD_MASTER), + EQUIP_CHOICE(ITEM_SHIELD_DEKU, "Deku Shield", EQUIP_TYPE_SHIELD, EQUIP_INV_SHIELD_DEKU), + EQUIP_CHOICE(ITEM_SHIELD_HYLIAN, "Hylian Shield", EQUIP_TYPE_SHIELD, EQUIP_INV_SHIELD_HYLIAN), + EQUIP_CHOICE(ITEM_SHIELD_MIRROR, "Mirror Shield", EQUIP_TYPE_SHIELD, EQUIP_INV_SHIELD_MIRROR), + EQUIP_CHOICE(ITEM_TUNIC_GORON, "Goron Tunic", EQUIP_TYPE_TUNIC, EQUIP_INV_TUNIC_GORON), + EQUIP_CHOICE(ITEM_TUNIC_ZORA, "Zora Tunic", EQUIP_TYPE_TUNIC, EQUIP_INV_TUNIC_ZORA), + EQUIP_CHOICE(ITEM_BOOTS_IRON, "Iron Boots", EQUIP_TYPE_BOOTS, EQUIP_INV_BOOTS_IRON), + EQUIP_CHOICE(ITEM_BOOTS_HOVER, "Hover Boots", EQUIP_TYPE_BOOTS, EQUIP_INV_BOOTS_HOVER), + { "ITEM_SCALE_SILVER", "Silver Scale", ITEM_SCALE_SILVER, [](int32_t _) { return CUR_UPG_VALUE(UPG_SCALE) == 0; }, + GiveItem }, + { "ITEM_SCALE_GOLDEN", "Golden Scale", ITEM_SCALE_GOLDEN, [](int32_t _) { return CUR_UPG_VALUE(UPG_SCALE) == 1; }, + GiveItem }, + { "ITEM_BRACELET", "Goron Bracelet", ITEM_BRACELET, [](int32_t _) { return CUR_UPG_VALUE(UPG_STRENGTH) == 0; }, + GiveItem }, + { "ITEM_GAUNTLETS_SILVER", "Silver Gauntlets", ITEM_GAUNTLETS_SILVER, + [](int32_t _) { return CUR_UPG_VALUE(UPG_STRENGTH) == 1; }, GiveItem }, + { "ITEM_GAUNTLETS_GOLD", "Golden Gauntlets", ITEM_GAUNTLETS_GOLD, + [](int32_t _) { return CUR_UPG_VALUE(UPG_STRENGTH) == 2; }, GiveItem }, + { "ITEM_MAGIC_SMALL", "Magic", ITEM_MAGIC_SMALL, [](int32_t _) { return !gSaveContext.isMagicAcquired; }, + UpgradeMagic }, + { "ITEM_MAGIC_LARGE", "Large Magic", ITEM_MAGIC_LARGE, + [](int32_t _) { return gSaveContext.isMagicAcquired && !gSaveContext.isDoubleMagicAcquired; }, UpgradeMagic }, +}; + +inline std::vector All = { + { "ITEM_SWORD_KOKIRI", "Stat Increase", 0, + [](uint32_t _) { + for (auto& statChoice : Stats) { + if (statChoice.canSelect(statChoice.value)) { + return true; + } + } + return false; + }, + Noop }, + { "ITEM_BOW", "Item", 1, + [](uint32_t _) { + for (auto& itemChoice : Items) { + if (itemChoice.canSelect(itemChoice.value)) { + return true; + } + } + return false; + }, + Noop }, + { "ITEM_SHIELD_DEKU", "Equipment", 2, + [](uint32_t _) { + for (auto& equipmentChoice : Equipment) { + if (equipmentChoice.canSelect(equipmentChoice.value)) { + return true; + } + } + return false; + }, + Noop }, + { "ITEM_SONG_MINUET", "Song", 3, + [](uint32_t _) { + for (auto& songChoice : Songs) { + if (songChoice.canSelect(songChoice.value)) { + return true; + } + } + return false; + }, + Noop }, +}; + +inline std::vector Choose3Max(std::vector& allChoices) { + std::vector choices = {}; + + for (auto& choice : allChoices) { + if (choice.canSelect(choice.value)) { + choices.push_back(&choice); + } + } + + Shuffle(choices); + + if (choices.size() > 3) { + choices.resize(3); + } + + return choices; +} + +} // namespace Choices + +} // namespace RogueLike + +#endif // ROGUELIKE_CHOICES_HPP diff --git a/soh/soh/Enhancements/RogueLike/Difficulty.cpp b/soh/soh/Enhancements/RogueLike/Difficulty.cpp new file mode 100644 index 00000000000..1b01bcebc2c --- /dev/null +++ b/soh/soh/Enhancements/RogueLike/Difficulty.cpp @@ -0,0 +1,115 @@ +#include "soh/Enhancements/RogueLike/RogueLike.h" +#include "soh/ShipInit.hpp" +#include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h" +#include "soh/ObjectExtension/ActorMaximumHealth.h" + +extern "C" { +#include "variables.h" +#include "functions.h" +#include "macros.h" + +extern PlayState* gPlayState; +} + +#define BASE_DIFFICULTY CVarGetFloat("gRogueLike.BaseDifficulty", 5000.0f) +#define GROWTH_RATE CVarGetFloat("gRogueLike.DifficultyGrowthRate", 1.3f) + +void RogueLike::Difficulty::IndicateActivity() { + gSaveContext.ship.quest.data.rogueLike.lastActivity = GetUnixTimestamp(); +} + +float RogueLike::Difficulty::GetProgressToNextLevel() { + u32 currentDifficulty = gSaveContext.ship.quest.data.rogueLike.difficulty; + + u32 currentLevel = GetCurrentLevel(); + u32 difficultyForCurrentLevel = ConvertLevelToDifficulty(currentLevel); + u32 difficultyForNextLevel = ConvertLevelToDifficulty(currentLevel + 1); + + return static_cast(currentDifficulty - difficultyForCurrentLevel) / + static_cast(difficultyForNextLevel - difficultyForCurrentLevel); +} + +u32 RogueLike::Difficulty::GetCurrentLevel() { + return RogueLike::Difficulty::ConvertDifficultyToLevel(gSaveContext.ship.quest.data.rogueLike.difficulty); +} + +u32 RogueLike::Difficulty::ConvertDifficultyToLevel(u32 difficulty) { + return static_cast(logf((difficulty * (GROWTH_RATE - 1) / BASE_DIFFICULTY) + 1) / logf(GROWTH_RATE)); +} + +u32 RogueLike::Difficulty::ConvertLevelToDifficulty(u32 level) { + return static_cast(BASE_DIFFICULTY * ((powf(GROWTH_RATE, level) - 1) / (GROWTH_RATE - 1))); +} + +void RogueLike::Difficulty::IncrementDifficulty(u32 amount) { + u32 oldLevel = GetCurrentLevel(); + gSaveContext.ship.quest.data.rogueLike.difficulty += amount; + u32 newLevel = GetCurrentLevel(); + + if (newLevel != oldLevel) { + // Loop over all actors and adjust health + + Actor* actor = gPlayState->actorCtx.actorLists[ACTORCAT_ENEMY].head; + while (actor != NULL) { + actor->colChkInfo.health = (actor->colChkInfo.health / (oldLevel + 1)) * (newLevel + 1); + SetActorMaximumHealth(actor, (GetActorMaximumHealth(actor) / (oldLevel + 1)) * (newLevel + 1)); + actor = actor->next; + } + } +} + +static void OnLoadGame() { + COND_HOOK(OnPlayerUpdate, IS_ROGUELIKE, []() { + if (GetUnixTimestamp() - gSaveContext.ship.quest.data.rogueLike.lastActivity >= 10 * 1000) { + RogueLike::Difficulty::IncrementDifficulty(1); + } + }); + + COND_HOOK(OnActorInit, IS_ROGUELIKE, [](void* refActor) { + Actor* actor = static_cast(refActor); + + if (actor->category != ACTORCAT_ENEMY) { + return; + } + + actor->colChkInfo.health *= (RogueLike::Difficulty::GetCurrentLevel() + 1); + }); + + COND_VB_SHOULD(VB_APPLY_DAMAGE_TO_ACTOR, IS_ROGUELIKE, { + Actor* actor = va_arg(args, Actor*); + u32 damageEffect = va_arg(args, u32); + u32 damage = va_arg(args, u32); + u32 dmgFlags = va_arg(args, u32); + + if (actor->category == ACTORCAT_PLAYER) { + SPDLOG_INFO("Incoming Damage Before: {}", damage); + + // Player taking damage, 1 Point addition per Difficulty Level + damage *= RogueLike::Difficulty::GetCurrentLevel() + 1; + + // Player taking damage, 1 Point reduction per Defense Level + damage /= (gSaveContext.ship.quest.data.rogueLike.stats[RL_DEFENSE] + 1); + + SPDLOG_INFO("Incoming Damage After: {}", damage); + } else if (actor->category == ACTORCAT_ENEMY) { + SPDLOG_INFO("Outgoing Damage Before: {}", damage); + + if (dmgFlags & DMG_SLASH_KOKIRI) { + // This was a Kokiri Sword attack, double damage bonus etc + // damage = static_cast(damage * 2.0f); + } + + // Enemy taking damage, 1 Point addition per Attack Level + damage *= gSaveContext.ship.quest.data.rogueLike.stats[RL_ATTACK] + 1; + + SPDLOG_INFO("Outgoing Damage After: {}", damage); + } else { + return; + } + + // Overwrite damage amount + actor->colChkInfo.damage = damage; + }); +} + +static RegisterShipInitFunc initFunc(OnLoadGame, { "IS_ROGUELIKE" }); diff --git a/soh/soh/Enhancements/RogueLike/Difficulty.h b/soh/soh/Enhancements/RogueLike/Difficulty.h new file mode 100644 index 00000000000..b8da10370ce --- /dev/null +++ b/soh/soh/Enhancements/RogueLike/Difficulty.h @@ -0,0 +1,24 @@ +#ifndef ROGUELIKE_DIFFICULTY_H +#define ROGUELIKE_DIFFICULTY_H + +extern "C" { +#include +} + +namespace RogueLike { + +namespace Difficulty { + +void IndicateActivity(); +float GetProgressToNextLevel(); +u32 GetCurrentLevel(); +u32 ConvertDifficultyToLevel(u32 difficulty); +u32 ConvertLevelToDifficulty(u32 level); +void IncrementDifficulty(u32 amount); +void OnLoadGame(); + +} // namespace Difficulty + +} // namespace RogueLike + +#endif diff --git a/soh/soh/Enhancements/RogueLike/GUI/GUI.cpp b/soh/soh/Enhancements/RogueLike/GUI/GUI.cpp new file mode 100644 index 00000000000..76577cce623 --- /dev/null +++ b/soh/soh/Enhancements/RogueLike/GUI/GUI.cpp @@ -0,0 +1,543 @@ +#include "soh/Enhancements/RogueLike/RogueLike.h" +#include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h" +#include "soh/ShipInit.hpp" +#include "soh/SohGui/SohMenu.h" +#include "soh/Enhancements/randomizer/3drando/random.hpp" + +extern "C" { +#include "variables.h" + +extern PlayState* gPlayState; +} + +namespace SohGui { +extern std::shared_ptr mSohMenu; +} + +#define QUEST_TO_TEXTURE(id) \ + { id, #id } + +std::map questToTexture = { + QUEST_TO_TEXTURE(QUEST_MEDALLION_FOREST), QUEST_TO_TEXTURE(QUEST_MEDALLION_FIRE), + QUEST_TO_TEXTURE(QUEST_MEDALLION_WATER), QUEST_TO_TEXTURE(QUEST_MEDALLION_SPIRIT), + QUEST_TO_TEXTURE(QUEST_MEDALLION_SHADOW), QUEST_TO_TEXTURE(QUEST_MEDALLION_LIGHT), + QUEST_TO_TEXTURE(QUEST_KOKIRI_EMERALD), QUEST_TO_TEXTURE(QUEST_GORON_RUBY), + QUEST_TO_TEXTURE(QUEST_ZORA_SAPPHIRE), QUEST_TO_TEXTURE(QUEST_STONE_OF_AGONY), + QUEST_TO_TEXTURE(QUEST_GERUDO_CARD), QUEST_TO_TEXTURE(QUEST_SKULL_TOKEN), +}; + +void RogueLike::GUI::BeginFullscreenDimmed(const char* windowName) { + ImGuiViewport* viewport = ImGui::GetMainViewport(); + ImGui::SetNextWindowPos(viewport->Pos); + ImGui::SetNextWindowSize(viewport->Size); + ImGui::SetNextWindowViewport(viewport->ID); + + ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0)); + ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0f); + ImGui::Begin(windowName, nullptr, + ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoScrollbar); + ImGui::PopStyleVar(); + ImGui::PopStyleVar(); +} + +static std::vector choices = {}; +static float rollTimer = 0.0f; +static int rollsRemaining = 0; + +RogueLike::Choices::ChoiceCard* +RogueLike::GUI::DrawChooseScreen(std::string heading, std::vector& allChoices, + int rolls) { + RogueLike::Choices::ChoiceCard* selectedChoice = nullptr; + + ImVec2 outerCardSize = ImVec2(300, 400); + ImVec2 innerCardSize = ImVec2(outerCardSize.x - 40, outerCardSize.y - 40); + ImVec2 iconSize = ImVec2(125, 125); + + // Heading + ImGui::SetWindowFontScale(2.0f); + ImGui::SetCursorPosY(ImGui::GetWindowHeight() / 2 - (outerCardSize.y / 2) - 100); + ImGui::SetCursorPosX((ImGui::GetWindowWidth() - ImGui::CalcTextSize(heading.c_str()).x) / 2); + ImGui::Text("%s", heading.c_str()); + ImGui::SetWindowFontScale(1.0f); + + // Cards + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0, 0)); + + if (choices.size() == 0) { + choices = RogueLike::Choices::Choose3Max(allChoices); + rollTimer = 0.0f; + if (choices.size() > 2) { + rollsRemaining = rolls; + choices.resize(2); + } else { + rollsRemaining = 0; + } + } + + if (rollsRemaining > 0) { + rollTimer += ImGui::GetIO().DeltaTime; + if (rollTimer >= 0.05f) { + rollTimer = 0.0f; + rollsRemaining--; + choices = RogueLike::Choices::Choose3Max(allChoices); + if (choices.size() > 2) { + choices.resize(2); + } + } + } + + float cardStartX = (ImGui::GetWindowWidth() - (outerCardSize.x * choices.size())) / 2; + float cardStartY = ImGui::GetWindowHeight() / 2 - (outerCardSize.y / 2); + + ImGui::SetCursorPosX(cardStartX); + ImGui::SetCursorPosY(cardStartY); + + static int cachedHoverIndex = -1; + int hoverIndex = -1; + + for (size_t i = 0; i < choices.size(); i++) { + if (i > 0) { + ImGui::SameLine(); + } + + ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(20, 20)); + ImGui::BeginChild(("card" + std::to_string(i)).c_str(), outerCardSize, ImGuiChildFlags_AlwaysUseWindowPadding); + ImGui::PopStyleVar(); + ImGui::PushStyleVar(ImGuiStyleVar_ChildRounding, 10.0f); + ImGui::PushStyleColor(ImGuiCol_ChildBg, (cachedHoverIndex == static_cast(i)) + ? ImVec4(0.2f, 0.2f, 0.2f, 1.0f) + : ImVec4(0.1f, 0.1f, 0.1f, 1.0f)); + ImGui::BeginChild(("card_content" + std::to_string(i)).c_str(), ImVec2(-1, -1), 0); + ImGui::PopStyleColor(); + ImGui::PopStyleVar(); + + if (choices[i]->textureName.substr(0, 10) == "QUEST_SONG") { + // Songs are thinner + iconSize = ImVec2(100, 150); + } + + ImGui::SetCursorPosX(innerCardSize.x / 2 - iconSize.x / 2); + ImGui::SetCursorPosY(innerCardSize.y / 2 - iconSize.y / 2 - 20); + ImTextureID textureId = + Ship::Context::GetInstance()->GetWindow()->GetGui()->GetTextureByName(choices[i]->textureName); + ImGui::Image(textureId, iconSize); + + ImGui::SetCursorPosX(innerCardSize.x / 2 - ImGui::CalcTextSize(choices[i]->name.c_str()).x / 2); + ImGui::SetCursorPosY(innerCardSize.y - 75); + if (rollsRemaining == 0) { + ImGui::Text("%s", choices[i]->name.c_str()); + } + + ImGui::EndChild(); + if (ImGui::IsItemHovered() && rollsRemaining == 0) { + hoverIndex = static_cast(i); + } + if (ImGui::IsItemClicked() && rollsRemaining == 0) { + selectedChoice = choices[i]; + } + ImGui::EndChild(); + } + + // Draw button indicators below each card + for (size_t i = 0; i < choices.size(); i++) { + // Draw button indicator + const char* buttonLabel = (i == 0) ? "B" : "A"; + float circleRadius = 30.0f; + ImVec2 windowPos = ImGui::GetWindowPos(); + ImVec2 circleCenter = ImVec2(windowPos.x + cardStartX + (outerCardSize.x * i) + (outerCardSize.x / 2), + windowPos.y + cardStartY + outerCardSize.y + 70); + + ImDrawList* drawList = ImGui::GetWindowDrawList(); + drawList->AddCircleFilled(circleCenter, circleRadius, + rollsRemaining == 0 + ? ((i == 0) ? IM_COL32(0, 150, 0, 255) : IM_COL32(20, 20, 190, 255)) + : IM_COL32(100, 100, 100, 255)); + drawList->AddCircle(circleCenter, circleRadius, IM_COL32(0, 0, 0, 100), 0, 4.0f); + + ImVec2 textSize = ImGui::CalcTextSize(buttonLabel); + ImVec2 textPos = ImVec2(circleCenter.x - textSize.x / 2, circleCenter.y - textSize.y / 2); + drawList->AddText(textPos, IM_COL32(255, 255, 255, 255), buttonLabel); + } + + if (hoverIndex != -1) { + cachedHoverIndex = hoverIndex; + } else { + cachedHoverIndex = -1; + } + + ImGui::PopStyleVar(); + + if (rollsRemaining == 0) { + Input* input = &gPlayState->state.input[0]; + + if (CHECK_BTN_ANY(input->press.button, BTN_A) && choices.size() > 1) { + selectedChoice = choices[1]; + input->press.button &= ~BTN_A; + } + if (CHECK_BTN_ANY(input->press.button, BTN_B)) { + selectedChoice = choices[0]; + input->press.button &= ~BTN_B; + } + } + + if (selectedChoice != nullptr) { + choices.clear(); + } + return selectedChoice; +} + +std::map> rogueLikeStatMap = { + { RL_HEALTH, { "Health", "ITEM_HEART_CONTAINER" } }, + { RL_ATTACK, { "Attack", "ITEM_SWORD_MASTER" } }, + { RL_DEFENSE, { "Defense", "ITEM_SHIELD_HYLIAN" } }, + { RL_SPEED, { "Speed", "ITEM_MASK_BUNNY" } }, +}; + +std::vector activeQuests; + +void TableCellVerticalCenteredText(ImVec4 color, const char* text) { + float textHeight = ImGui::GetTextLineHeight(); + float offsetX = (32.0f - textHeight + 10.0f) * 0.5f; + ImGui::SetCursorPosY(ImGui::GetCursorPosY() + offsetX); + ImGui::TextColored(color, text); +} + +void TableCellHorizontalCenteredText(ImVec4 color, const char* text) { + float cellWidth = ImGui::GetContentRegionAvail().x; + float textWidth = ImGui::CalcTextSize(text).x; + float offsetX = (cellWidth - textWidth) * 0.5f; + ImGui::SetCursorPosX(ImGui::GetCursorPosX() + offsetX); + ImGui::TextColored(color, text); +} + +bool TableCellCenteredImageButton(const char* id, ImTextureID texture) { + float cellWidth = ImGui::GetContentRegionAvail().x; + float offsetX = (cellWidth - 46.0f) * 0.5f; + + ImGui::SetCursorPosX(ImGui::GetCursorPosX() + offsetX); + return ImGui::ImageButton(id, texture, ImVec2(46.0f, 46.0f)); +} + +void RogueLike::GUI::HUDWindow::Draw() { + if (!IsVisible()) { + return; + } + + // Full screen overlay + ImGuiViewport* viewport = ImGui::GetMainViewport(); + ImGui::SetNextWindowPos(viewport->Pos); + ImGui::SetNextWindowSize(viewport->Size); + ImGui::SetNextWindowViewport(viewport->ID); + + ImGui::Begin("RogueLike HUD", nullptr, + ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoBackground); + + ImGui::PushStyleColor(ImGuiCol_Border, ImVec4(0, 0, 0, 0)); + ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 4.0f); + if (ImGui::BeginChild("StatsWindow")) { + if (ImGui::BeginTable("StatsList", 3, ImGuiTableFlags_SizingFixedFit)) { + ImTextureID textureId = + Ship::Context::GetInstance()->GetWindow()->GetGui()->GetTextureByName("ITEM_RUPEE_GREEN"); + ImGui::TableNextColumn(); + ImGui::Image(textureId, ImVec2(46.0f, 46.0f)); + + ImGui::TableNextColumn(); + TableCellVerticalCenteredText(ImVec4(1, 1, 1, 1), "Level"); + + ImGui::TableNextColumn(); + ImGui::SetCursorPosY(ImGui::GetCursorPosY() + 9.0f); + ImGui::ProgressBar(RogueLike::XP::GetProgressToNextLevel(), ImVec2(200, 0), + (std::to_string(RogueLike::XP::GetCurrentLevel()) + " (" + + std::to_string(static_cast(RogueLike::XP::GetProgressToNextLevel() * 100)) + "%)") + .c_str()); + + ImTextureID textureId2 = + Ship::Context::GetInstance()->GetWindow()->GetGui()->GetTextureByName("ITEM_MASK_SKULL"); + ImGui::TableNextColumn(); + ImGui::Image(textureId2, ImVec2(46.0f, 46.0f)); + + ImGui::TableNextColumn(); + TableCellVerticalCenteredText(ImVec4(1, 1, 1, 1), "Difficulty"); + + ImGui::TableNextColumn(); + ImGui::SetCursorPosY(ImGui::GetCursorPosY() + 9.0f); + ImGui::ProgressBar( + RogueLike::Difficulty::GetProgressToNextLevel(), ImVec2(200, 0), + (std::to_string(RogueLike::Difficulty::GetCurrentLevel()) + " (" + + std::to_string(static_cast(RogueLike::Difficulty::GetProgressToNextLevel() * 100)) + "%)") + .c_str()); + + for (auto& stat : rogueLikeStatMap) { + ImTextureID textureId = + Ship::Context::GetInstance()->GetWindow()->GetGui()->GetTextureByName(stat.second.second); + std::string statValueStr = gSaveContext.ship.quest.data.rogueLike.stats[stat.first] >= 0 ? "+" : "-"; + statValueStr += std::to_string(gSaveContext.ship.quest.data.rogueLike.stats[stat.first]).c_str(); + + ImGui::TableNextColumn(); + ImGui::Image(textureId, ImVec2(46.0f, 46.0f)); + + ImGui::TableNextColumn(); + TableCellVerticalCenteredText(ImVec4(1, 1, 1, 1), stat.second.first.c_str()); + + ImGui::TableNextColumn(); + TableCellVerticalCenteredText(ImVec4(0, 1, 0, 1), statValueStr.c_str()); + } + + ImGui::TableNextColumn(); + ImGui::TableNextColumn(); + TableCellVerticalCenteredText(ImVec4(1, 1, 1, 1), "Required Dungeon Rewards:"); + ImGui::TableNextColumn(); + ImGui::TableNextColumn(); + ImGui::TableNextColumn(); + for (auto& reward : RogueLike::requiredRewards) { + if (reward != RogueLike::requiredRewards[0]) { + ImGui::SameLine(); + } + auto tint = CHECK_QUEST_ITEM(reward) ? ImVec4(1, 1, 1, 1) : ImVec4(1, 1, 1, 0.5f); + ImTextureID textureId = + Ship::Context::GetInstance()->GetWindow()->GetGui()->GetTextureByName(questToTexture[reward]); + ImGui::Image(textureId, ImVec2(25.0f, 25.0f), ImVec2(0.0f, 0.0f), ImVec2(1, 1), tint, + ImVec4(0, 0, 0, 0)); + } + + ImGui::EndTable(); + + if (ImGui::BeginChild("QuestWindow", ImVec2(300.0f, 0))) { + if (activeQuests.size() != 0) { + ImVec4 completionColor = ImVec4(1, 1, 1, 1); + for (auto& quests : activeQuests) { + if (quests.questProgress == quests.questGoal || quests.questStatus == RL_QUEST_COMPLETE) { + completionColor = ImVec4(0, 1, 0, 1); + } + + ImGui::SeparatorText(quests.questName); + if (quests.questStatus != RL_QUEST_COMPLETE) { + ImGui::Text(quests.questDescription); + std::string questProgressStr = std::to_string(quests.questProgress).c_str(); + questProgressStr += " / "; + questProgressStr += std::to_string(quests.questGoal).c_str(); + TableCellHorizontalCenteredText(completionColor, questProgressStr.c_str()); + } else { + TableCellHorizontalCenteredText(completionColor, "Quest Complete"); + } + + ImGui::Separator(); + } + } + ImGui::EndChild(); + } + } + ImGui::EndChild(); + } + ImGui::PopStyleColor(1); + ImGui::PopStyleVar(1); + + ImGui::End(); +} + +std::shared_ptr mStartingSelectionWindow; +std::shared_ptr mHUDWindow; +std::shared_ptr mLevelUpWindow; + +// Entry point for the module, run once on game boot +static void InitRogueLikeGUI() { + CVarClear(CVAR_WINDOW("RogueLikeStartingSelection")); + CVarClear(CVAR_WINDOW("RogueLikeHUD")); + + auto gui = Ship::Context::GetInstance()->GetWindow()->GetGui(); + + mStartingSelectionWindow = std::make_shared( + CVAR_WINDOW("RogueLikeStartingSelection"), "RogueLike Starting Selection"); + gui->AddGuiWindow(mStartingSelectionWindow); + + mHUDWindow = std::make_shared(CVAR_WINDOW("RogueLikeHUD"), "RogueLike HUD"); + gui->AddGuiWindow(mHUDWindow); + + mLevelUpWindow = + std::make_shared(CVAR_WINDOW("RogueLikeLevelUp"), "RogueLike Level Up"); + gui->AddGuiWindow(mLevelUpWindow); + + SohGui::mSohMenu->AddSidebarEntry("Holiday", "RogueLike", 2); + WidgetPath path = { "Holiday", "RogueLike", SECTION_COLUMN_2 }; + SohGui::mSohMenu->AddWidget(path, "RogueLikeRight", WIDGET_CUSTOM).CustomFunction([](WidgetInfo& info) { + if (UIWidgets::Button("Reset All", UIWidgets::ButtonOptions().Size(UIWidgets::Sizes::Inline))) { + CVarSetFloat("gRogueLike.BaseDifficulty", 5000.0f); + CVarSetFloat("gRogueLike.DifficultyGrowthRate", 1.3f); + CVarSetFloat("gRogueLike.BaseXP", 100.0f); + CVarSetFloat("gRogueLike.XPGrowthRate", 1.3f); + CVarSetInteger("gRogueLike.EnemyPlateMax", 3); + CVarSetInteger("gRogueLike.EnemyPlateChance", 25); + + CVarSetInteger("gRogueLike.XPDrop.Enemies", 50); + CVarSetInteger("gRogueLike.XPDrop.Bosses", 200); + CVarSetInteger("gRogueLike.XPDrop.BusinessScrubs", 100); + CVarSetInteger("gRogueLike.XPDrop.Chests", 50); + CVarSetInteger("gRogueLike.XPDrop.Grass", 20); + CVarSetInteger("gRogueLike.XPDrop.Pots", 20); + CVarSetInteger("gRogueLike.XPDrop.Rocks", 20); + CVarSetInteger("gRogueLike.XPDrop.Trees", 20); + } + + ImGui::SeparatorText("Scaling Options:"); + + UIWidgets::CVarSliderFloat( + "Base Difficulty", "gRogueLike.BaseDifficulty", + UIWidgets::FloatSliderOptions().Min(0.0f).Max(10000.0f).DefaultValue(5000.0f).Size(ImVec2(300.0f, 0.0f))); + + UIWidgets::CVarSliderFloat( + "Difficulty Growth Rate", "gRogueLike.DifficultyGrowthRate", + UIWidgets::FloatSliderOptions().Min(0.0f).Max(5.0f).DefaultValue(1.3f).Size(ImVec2(300.0f, 0.0f))); + + UIWidgets::CVarSliderFloat( + "Base XP Req", "gRogueLike.BaseXP", + UIWidgets::FloatSliderOptions().Min(0.0f).Max(1000.0f).DefaultValue(100.0f).Size(ImVec2(300.0f, 0.0f))); + + UIWidgets::CVarSliderFloat( + "XP Growth Rate", "gRogueLike.XPGrowthRate", + UIWidgets::FloatSliderOptions().Min(0.0f).Max(5.0f).DefaultValue(1.3f).Size(ImVec2(300.0f, 0.0f))); + + UIWidgets::CVarSliderInt( + "Maximum Enemies Plated", "gRogueLike.EnemyPlateMax", + UIWidgets::IntSliderOptions().Min(0).Max(10).DefaultValue(3).Size(ImVec2(300.0f, 0.0f))); + + UIWidgets::CVarSliderInt( + "Chance of Enemy Plating", "gRogueLike.EnemyPlateChance", + UIWidgets::IntSliderOptions().Min(0).Max(100).DefaultValue(25).Size(ImVec2(300.0f, 0.0f))); + + ImGui::SeparatorText("XP Drop Rates:"); + + UIWidgets::CVarSliderInt( + "Enemies", "gRogueLike.XPDrop.Enemies", + UIWidgets::IntSliderOptions().Min(1).Max(1000).DefaultValue(50).Size(ImVec2(300.0f, 0.0f))); + + UIWidgets::CVarSliderInt( + "Bosses", "gRogueLike.XPDrop.Bosses", + UIWidgets::IntSliderOptions().Min(1).Max(5000).DefaultValue(200).Size(ImVec2(300.0f, 0.0f))); + + UIWidgets::CVarSliderInt( + "Business Scrubs", "gRogueLike.XPDrop.BusinessScrubs", + UIWidgets::IntSliderOptions().Min(1).Max(5000).DefaultValue(100).Size(ImVec2(300.0f, 0.0f))); + + UIWidgets::CVarSliderInt( + "Chests", "gRogueLike.XPDrop.Chests", + UIWidgets::IntSliderOptions().Min(1).Max(1000).DefaultValue(50).Size(ImVec2(300.0f, 0.0f))); + + UIWidgets::CVarSliderInt( + "Grass", "gRogueLike.XPDrop.Grass", + UIWidgets::IntSliderOptions().Min(1).Max(5000).DefaultValue(20).Size(ImVec2(300.0f, 0.0f))); + + UIWidgets::CVarSliderInt( + "Pots", "gRogueLike.XPDrop.Pots", + UIWidgets::IntSliderOptions().Min(1).Max(1000).DefaultValue(20).Size(ImVec2(300.0f, 0.0f))); + + UIWidgets::CVarSliderInt( + "Rocks", "gRogueLike.XPDrop.Rocks", + UIWidgets::IntSliderOptions().Min(1).Max(5000).DefaultValue(20).Size(ImVec2(300.0f, 0.0f))); + + UIWidgets::CVarSliderInt( + "Trees", "gRogueLike.XPDrop.Trees", + UIWidgets::IntSliderOptions().Min(1).Max(5000).DefaultValue(20).Size(ImVec2(300.0f, 0.0f))); + }); + + path = { "Holiday", "RogueLike", SECTION_COLUMN_1 }; + SohGui::mSohMenu->AddWidget(path, "RogueLikeLeft", WIDGET_CUSTOM).CustomFunction([](WidgetInfo& info) { + ImGui::TextWrapped( + "RogueLike mode is an unpolished proof of concept that enables you to play through the game doing " + "various things to gain XP and gaining items and progression through random rolls instead of finding them " + "at specific points in the game. There are various settings to tweak the balance on the right hand panel, " + "the current balance has not really been heavily tested so feel free to experiment and share your " + "findings. Also if there is interest some one is welcome to pick this up and polish it into a more " + "complete mode, all of it is open source.\n\n" + "\n" + "To begin, start a new file and select the RogueLike quest mode. Your goal is to beat a random selection " + "of dungeons, which will grant you Ganon's boss key and allow you to finish the game."); + + ImGui::SeparatorText("Cheats:"); + + if (!IS_ROGUELIKE) { + ImGui::TextColored(ImVec4(1, 0, 0, 1), "Must be in a RogueLike save"); + return; + } + + if (UIWidgets::Button("Add XP Level", UIWidgets::ButtonOptions().Size(UIWidgets::Sizes::Inline))) { + RogueLike::XP::GrantXP(RogueLike::XP::ConvertLevelToXP(RogueLike::XP::GetCurrentLevel() + 1) - + gSaveContext.ship.quest.data.rogueLike.xp + 1); + } + ImGui::SameLine(); + if (UIWidgets::Button("Remove XP Level", UIWidgets::ButtonOptions().Size(UIWidgets::Sizes::Inline))) { + gSaveContext.ship.quest.data.rogueLike.xp = + RogueLike::XP::ConvertLevelToXP(RogueLike::XP::GetCurrentLevel() - 1); + } + if (UIWidgets::Button("Add Difficulty Level", UIWidgets::ButtonOptions().Size(UIWidgets::Sizes::Inline))) { + RogueLike::Difficulty::IncrementDifficulty( + RogueLike::Difficulty::ConvertLevelToDifficulty(RogueLike::Difficulty::GetCurrentLevel() + 1) - + gSaveContext.ship.quest.data.rogueLike.difficulty + 1); + } + ImGui::SameLine(); + if (UIWidgets::Button("Remove Difficulty Level", UIWidgets::ButtonOptions().Size(UIWidgets::Sizes::Inline))) { + gSaveContext.ship.quest.data.rogueLike.difficulty = + RogueLike::Difficulty::ConvertLevelToDifficulty(RogueLike::Difficulty::GetCurrentLevel() - 1); + } + + std::string statPlusValue = ""; + std::string statMinusValue = ""; + if (ImGui::BeginTable("Stat Testing", 2)) { + for (auto& stat : rogueLikeStatMap) { + ImTextureID textureId = + Ship::Context::GetInstance()->GetWindow()->GetGui()->GetTextureByName(stat.second.second); + statPlusValue = "+ "; + statMinusValue = "- "; + std::string statValueStr = stat.second.first; + statPlusValue += statValueStr; + statMinusValue += statValueStr; + + ImGui::TableNextColumn(); + if (ImGui::ImageButton(statPlusValue.c_str(), textureId, ImVec2(46.0f, 46.0f))) { + gSaveContext.ship.quest.data.rogueLike.stats[stat.first]++; + RogueLike::XP::UpdatePlayerStats(); + } + ImGui::TextColored(ImVec4(0, 1, 0, 1), statPlusValue.c_str()); + + ImGui::TableNextColumn(); + if (ImGui::ImageButton(statMinusValue.c_str(), textureId, ImVec2(46.0f, 46.0f))) { + gSaveContext.ship.quest.data.rogueLike.stats[stat.first]--; + RogueLike::XP::UpdatePlayerStats(); + } + ImGui::TextColored(ImVec4(1, 0, 0, 1), statMinusValue.c_str()); + } + ImGui::EndTable(); + } + }); + + COND_HOOK(OnExitGame, true, [](int32_t fileNum) { + mStartingSelectionWindow->Hide(); + mHUDWindow->Hide(); + mLevelUpWindow->Hide(); + }); +} + +static void OnLoadGame() { + mStartingSelectionWindow->Hide(); + mHUDWindow->Hide(); + mLevelUpWindow->Hide(); + + if (IS_ROGUELIKE) { + if (gSaveContext.ship.quest.data.rogueLike.lastActivity == 0) { + RogueLike::Difficulty::IndicateActivity(); + mStartingSelectionWindow->Show(); + } + } + + COND_HOOK(OnPlayerUpdate, IS_ROGUELIKE, [] { + if (mStartingSelectionWindow->IsVisible() || mLevelUpWindow->IsVisible()) { + mHUDWindow->Hide(); + gPlayState->frameAdvCtx.enabled = true; + } else { + mHUDWindow->Show(); + gPlayState->frameAdvCtx.enabled = false; + } + }); +} + +static RegisterMenuInitFunc menuInitFunc(InitRogueLikeGUI); +static RegisterShipInitFunc initFunc2(OnLoadGame, { "IS_ROGUELIKE" }); diff --git a/soh/soh/Enhancements/RogueLike/GUI/GUI.h b/soh/soh/Enhancements/RogueLike/GUI/GUI.h new file mode 100644 index 00000000000..33cc68c3a2b --- /dev/null +++ b/soh/soh/Enhancements/RogueLike/GUI/GUI.h @@ -0,0 +1,59 @@ +#ifndef ROGUELIKE_GUI_H +#define ROGUELIKE_GUI_H + +#include +#include +#include +#include "soh/Enhancements/RogueLike/Choices.hpp" + +namespace RogueLike { + +namespace GUI { + +void BeginFullscreenDimmed(const char* windowName); +RogueLike::Choices::ChoiceCard* DrawChooseScreen(std::string heading, + std::vector& allChoices, int rolls); + +class StartingSelectionWindow final : public Ship::GuiWindow { + public: + using GuiWindow::GuiWindow; + void Draw() override; + ~StartingSelectionWindow(){}; + + protected: + void InitElement() override{}; + void DrawElement() override{}; + void UpdateElement() override{}; +}; + +class HUDWindow final : public Ship::GuiWindow { + public: + using GuiWindow::GuiWindow; + void Draw() override; + ~HUDWindow(){}; + + protected: + void InitElement() override{}; + void DrawElement() override{}; + void UpdateElement() override{}; +}; + +class LevelUpWindow final : public Ship::GuiWindow { + public: + using GuiWindow::GuiWindow; + void Draw() override; + ~LevelUpWindow(){}; + + protected: + void InitElement() override{}; + void DrawElement() override{}; + void UpdateElement() override{}; +}; + +extern std::shared_ptr mLevelUpWindow; + +} // namespace GUI + +} // namespace RogueLike + +#endif diff --git a/soh/soh/Enhancements/RogueLike/GUI/LevelUpWindow.cpp b/soh/soh/Enhancements/RogueLike/GUI/LevelUpWindow.cpp new file mode 100644 index 00000000000..8b379084724 --- /dev/null +++ b/soh/soh/Enhancements/RogueLike/GUI/LevelUpWindow.cpp @@ -0,0 +1,74 @@ +#include "soh/Enhancements/RogueLike/RogueLike.h" + +extern "C" { +#include "variables.h" +#include "macros.h" +#include "functions.h" + +extern PlayState* gPlayState; +} + +typedef enum { + WINDOW_STATE_CHOOSE_TYPE, + WINDOW_STATE_CHOOSE_CARD, + WINDOW_STATE_APPLY, +} LevelUpWindowState; + +static LevelUpWindowState state = WINDOW_STATE_CHOOSE_TYPE; +static RogueLike::Choices::ChoiceCard* typeChoice = nullptr; +static RogueLike::Choices::ChoiceCard* cardChoice = nullptr; + +void RogueLike::GUI::LevelUpWindow::Draw() { + if (!IsVisible()) { + return; + } + + BeginFullscreenDimmed("RogueLike Level Up"); + + switch (state) { + case WINDOW_STATE_CHOOSE_TYPE: { + typeChoice = DrawChooseScreen("Choose.", RogueLike::Choices::All, 10); + if (typeChoice != nullptr) { + state = WINDOW_STATE_CHOOSE_CARD; + } + } break; + case WINDOW_STATE_CHOOSE_CARD: { + switch (typeChoice->value) { + case 0: // Stat + cardChoice = DrawChooseScreen("Choose.", RogueLike::Choices::Stats, 10); + break; + case 1: // Item + cardChoice = DrawChooseScreen("Choose.", RogueLike::Choices::Items, 10); + break; + case 2: // Equipment + cardChoice = DrawChooseScreen("Choose.", RogueLike::Choices::Equipment, 10); + break; + case 3: // Song + cardChoice = DrawChooseScreen("Choose.", RogueLike::Choices::Songs, 10); + break; + default: + break; + } + + if (cardChoice != nullptr) { + state = WINDOW_STATE_APPLY; + } + } break; + default: { + // Apply choices + cardChoice->onSelect(cardChoice->value); + RogueLike::XP::UpdatePlayerStats(); + + // Close window and continue game + this->Hide(); + gPlayState->frameAdvCtx.enabled = false; + + // Reset state + state = WINDOW_STATE_CHOOSE_TYPE; + typeChoice = nullptr; + cardChoice = nullptr; + } break; + } + + ImGui::End(); +} diff --git a/soh/soh/Enhancements/RogueLike/GUI/StartingSelectionWindow.cpp b/soh/soh/Enhancements/RogueLike/GUI/StartingSelectionWindow.cpp new file mode 100644 index 00000000000..9e51ba4dc87 --- /dev/null +++ b/soh/soh/Enhancements/RogueLike/GUI/StartingSelectionWindow.cpp @@ -0,0 +1,71 @@ +#include "soh/Enhancements/RogueLike/RogueLike.h" + +extern "C" { +#include "variables.h" +#include "macros.h" +#include "functions.h" + +extern PlayState* gPlayState; +} + +typedef enum { + WINDOW_STATE_LOCATION, + WINDOW_STATE_ITEM, + WINDOW_STATE_SONG, + WINDOW_STATE_APPLY, +} StartingSelectionWindowState; + +static StartingSelectionWindowState state = WINDOW_STATE_LOCATION; +static RogueLike::Choices::ChoiceCard* locationChoice = nullptr; +static RogueLike::Choices::ChoiceCard* itemChoice = nullptr; +static RogueLike::Choices::ChoiceCard* songChoice = nullptr; + +void RogueLike::GUI::StartingSelectionWindow::Draw() { + if (!IsVisible()) { + return; + } + + BeginFullscreenDimmed("RogueLike Starting Selection"); + + switch (state) { + case WINDOW_STATE_LOCATION: { + locationChoice = DrawChooseScreen("Where will you begin your journey?", RogueLike::Choices::Locations, 20); + if (locationChoice != nullptr) { + state = WINDOW_STATE_ITEM; + } + } break; + case WINDOW_STATE_ITEM: { + itemChoice = DrawChooseScreen("What will you take?", RogueLike::Choices::Items, 20); + + if (itemChoice != nullptr) { + state = WINDOW_STATE_SONG; + } + } break; + case WINDOW_STATE_SONG: { + songChoice = DrawChooseScreen("What is your tune of choice?", RogueLike::Choices::Songs, 20); + + if (songChoice != nullptr) { + state = WINDOW_STATE_APPLY; + } + } break; + default: { + // Apply choices + locationChoice->onSelect(locationChoice->value); + itemChoice->onSelect(itemChoice->value); + songChoice->onSelect(songChoice->value); + + // Close window and reload game state + this->Hide(); + SET_NEXT_GAMESTATE(&gPlayState->state, Play_Init, PlayState); + gPlayState->state.running = false; + + // Reset state + state = WINDOW_STATE_LOCATION; + locationChoice = nullptr; + itemChoice = nullptr; + songChoice = nullptr; + } break; + } + + ImGui::End(); +} diff --git a/soh/soh/Enhancements/RogueLike/MiscBehavior/OnLoadGame.cpp b/soh/soh/Enhancements/RogueLike/MiscBehavior/OnLoadGame.cpp new file mode 100644 index 00000000000..ca204f9807e --- /dev/null +++ b/soh/soh/Enhancements/RogueLike/MiscBehavior/OnLoadGame.cpp @@ -0,0 +1,38 @@ +#include "soh/Enhancements/RogueLike/RogueLike.h" +#include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h" +#include "soh/ShipInit.hpp" + +extern "C" { +#include "variables.h" +#include "functions.h" +} + +static void OnLoadGame(int32_t fileNum) { + ShipInit::Init("IS_ROGUELIKE"); + + if (IS_ROGUELIKE) { + Flags_SetEventChkInf(EVENTCHKINF_OPENED_THE_DOOR_OF_TIME); + Flags_SetEventChkInf(EVENTCHKINF_SHOWED_MIDO_SWORD_SHIELD); + Flags_SetEventChkInf(EVENTCHKINF_SPOKE_TO_MIDO_AFTER_DEKU_TREES_DEATH); + } + + // Pick 4 random rewards for this run, seeded by the filecreatedat + RogueLike::requiredRewards.clear(); + Random_Init(gSaveContext.ship.stats.fileCreatedAt); + std::vector rewards = { + QUEST_MEDALLION_FOREST, QUEST_MEDALLION_FIRE, QUEST_MEDALLION_WATER, + QUEST_MEDALLION_SPIRIT, QUEST_MEDALLION_SHADOW, QUEST_MEDALLION_LIGHT, + QUEST_KOKIRI_EMERALD, QUEST_GORON_RUBY, QUEST_ZORA_SAPPHIRE, + }; + + // Shuffle and pick first 4 + while (RogueLike::requiredRewards.size() < 4) { + size_t index = Random(0, rewards.size() - 1); + uint32_t reward = rewards[index]; + rewards.erase(rewards.begin() + index); + RogueLike::requiredRewards.push_back(reward); + } +} + +static RegisterShipInitFunc + initFunc([]() { GameInteractor::Instance->RegisterGameHook(OnLoadGame); }, {}); diff --git a/soh/soh/Enhancements/RogueLike/Quests.cpp b/soh/soh/Enhancements/RogueLike/Quests.cpp new file mode 100644 index 00000000000..c1fc1a637d2 --- /dev/null +++ b/soh/soh/Enhancements/RogueLike/Quests.cpp @@ -0,0 +1,681 @@ +#include "soh/Enhancements/RogueLike/RogueLike.h" +#include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h" +#include "soh/ShipInit.hpp" +#include "soh/ActorDB.h" +#include "soh/Enhancements/custom-message/CustomMessageManager.h" +#include "soh/Notification/Notification.h" + +extern "C" { +#include "variables.h" +#include +#include + +#include "overlays/actors/ovl_Bg_Mjin/z_bg_mjin.h" +#include "overlays/actors/ovl_En_Vm/z_en_vm.h" + +extern PlayState* gPlayState; +s32 Object_Spawn(ObjectContext* objectCtx, s16 objectId); +} + +// clang-format off +std::vector rogueLikeQuestList = { + { RL_QUEST_HF_TRIAL_A, "Ganon's Fury I", "Watch out!", RL_QUEST_ACTIVE, 0, 1 }, + { RL_QUEST_HF_TRIAL_B, "Ganon's Fury II", "Get to Gerudo Valley\nbefore time runs out!", RL_QUEST_ACTIVE, 0, 1 }, + { RL_QUEST_KF_HOPOFFAITH, "Hop of Faith", "Sidehop from the fence\nabove the waterfall and land\non the middle platform.", RL_QUEST_ACTIVE, 0, 1}, + { RL_QUEST_KF_STRONGMAN, "Toe Crushers", "Mido likes rock, show them\nthat we don't!", RL_QUEST_ACTIVE, 0, 11 }, + { RL_QUEST_KV_POTHUNT, "The Pot Thickens", "A magical pot with extra lives?\nFind out how many!", RL_QUEST_ACTIVE, 0, 4 }, + { RL_QUEST_KV_STALFOS, "Stal-Not-So-Child", "The Stalchild in Hyrule Field have\ngotten bigger, take them out!", RL_QUEST_ACTIVE, 0, 5 }, + { RL_QUEST_ZD_POTTERY, "A Smashing View", "Toss a pot off the edge\nof the waterfall.", RL_QUEST_ACTIVE, 0, 1 }, +}; + +std::vector potHuntLocations = { + { 150.378f, 300.0f, 1166.648f }, + { 6.151f, 755.0f, -91.802f }, + { 1760.740f, 542.62f, 534.236f }, + { -381.964f, 240.0f, 1597.884f }, +}; + +const std::vector> trialAActorSpawnList = { + { { 563.838f, -0, 3059.409f }, -27275 }, + { { 678.031f, -0, 2563.164f }, -10891 }, + { { 185.132f, -0, 2404.207f }, -27275 }, + { { 36.108f, -0, 2925.440f }, -10891 }, + { { 335.571f, 20.0f, 2677.854f }, 0 }, +}; +// clang-format on + +Actor* trialActorSlot = NULL; +extern std::vector activeQuests; +static std::vector potHuntAvailability; +static std::vector currentTrialActorList; +static bool potHuntActorSpawned = false; +static bool sendConditionMessage = true; + +bool CheckActiveQuestById(u8 questId) { + for (auto& quest : activeQuests) { + if (quest.questId == questId) { + return true; + } + } + return false; +} + +bool CheckQuestGoalCompleteById(u8 questId) { + for (auto& quest : activeQuests) { + if (quest.questId == questId) { + return (quest.questProgress == quest.questGoal); + } + } + return false; +} + +bool CheckQuestCompletedById(u8 questId) { + for (auto& quest : activeQuests) { + if (quest.questId == questId) { + return (quest.questStatus); + } + } + return false; +} + +void SendQuestConditionMessage(u8 questId) { + if (!sendConditionMessage) { + return; + } + + std::string message = ""; + ImVec4 color = ImVec4(1, 1, 1, 1); + + switch (questId) { + case RL_QUEST_KV_POTHUNT: + message = "A Magical Pot has appeared nearby."; + color = ImVec4(0, 0.25f, 0.75f, 1); + break; + case RL_QUEST_HF_TRIAL_A: + message = "Come back when you have a sword..."; + color = ImVec4(1, 0, 0, 1); + break; + case RL_QUEST_HF_TRIAL_B: + message = "Come back when you're faster..."; + color = ImVec4(1, 0, 0, 1); + break; + default: + return; + } + Notification::Emit({ + .message = message, + .messageColor = color, + }); + sendConditionMessage = false; +} + +void RogueLike::Quests::CompleteQuestById(u8 questId) { + for (auto& quest : activeQuests) { + if (quest.questId == questId) { + quest.questStatus = RL_QUEST_COMPLETE; + break; + } + } +} + +void RogueLike::Quests::RemoveQuestById(u8 questId) { + int index = -1; + for (int i = 0; i < activeQuests.size(); i++) { + if (activeQuests[i].questId == questId) { + index = i; + break; + } + } + + if (index != -1) { + activeQuests.erase(activeQuests.begin() + index); + } +} + +void RogueLike::Quests::AddQuestById(u8 questId) { + if (CheckActiveQuestById(questId)) { + return; + } + + activeQuests.push_back(rogueLikeQuestList[questId]); +} + +u16 GetQuestProgress(u8 questId) { + if (!CheckActiveQuestById(questId)) { + return 0; + } + + for (auto& quest : activeQuests) { + if (quest.questId == questId) { + return quest.questProgress; + } + } + + return 0; +} + +u16 GetQuestGoal(u8 questId) { + if (!CheckActiveQuestById(questId)) { + return 0; + } + + for (auto& quest : activeQuests) { + if (quest.questId == questId) { + return quest.questGoal; + } + } + + return 0; +} + +u16 DetermineInitialQuestProgress(u8 questId, int16_t progressActor) { + ActorListEntry actorList = gPlayState->actorCtx.actorLists[ACTORCAT_PROP]; + u16 initialProgress = GetQuestGoal(questId); + if (questId == RL_QUEST_KF_STRONGMAN) { + initialProgress++; + } + + Actor* currentActor = actorList.head; + while (currentActor != nullptr) { + if (currentActor->id == progressActor) { + initialProgress--; + } + currentActor = currentActor->next; + } + + return initialProgress; +} + +void RogueLike::Quests::UpdateQuestProgress(u8 questId) { + if (!CheckActiveQuestById(questId)) { + return; + } + + for (auto& quest : activeQuests) { + if (quest.questId == questId) { + quest.questProgress++; + break; + } + } +} + +void RogueLike::Quests::SetQuestProgress(u8 questId, u16 progress) { + if (!CheckActiveQuestById(questId)) { + return; + } + + for (auto& quest : activeQuests) { + if (quest.questId == questId) { + quest.questProgress = progress; + break; + } + } +} + +void RogueLike::Quests::ResetQuestProgress(u8 questId) { + if (!CheckActiveQuestById(questId)) { + return; + } + + if (CheckQuestGoalCompleteById(questId)) { + return; + } + + for (auto& quest : activeQuests) { + if (quest.questId == questId) { + if (questId == RL_QUEST_KV_POTHUNT) { + potHuntAvailability.clear(); + } + quest.questProgress = 0; + break; + } + } +} + +// StartQuest() - Used for Quest Specific Functions. +void StartQuest(u8 questId) { + uint32_t index = 0; + switch (questId) { + case RL_QUEST_HF_TRIAL_A: + if (!CheckQuestGoalCompleteById(RL_QUEST_HF_TRIAL_A)) { + GameInteractor::RawAction::SetWeatherStorm(true); + for (auto& slot : trialAActorSpawnList) { + if (slot.first.y != 20.0f) { + trialActorSlot = + Actor_Spawn(&gPlayState->actorCtx, gPlayState, ACTOR_BG_HIDAN_FWBIG, slot.first.x, + slot.first.y, slot.first.z, 0, slot.second, 0, 0, false); + currentTrialActorList.push_back(trialActorSlot); + } else { + EnVm* beamosActor = + (EnVm*)Actor_Spawn(&gPlayState->actorCtx, gPlayState, ACTOR_EN_VM, slot.first.x, + slot.first.y, slot.first.z, 0, slot.second, 0, 0, false); + beamosActor->beamSightRange = 400.0f; + currentTrialActorList.push_back(&beamosActor->actor); + } + index++; + } + Actor_Spawn(&gPlayState->actorCtx, gPlayState, ACTOR_EN_ZF, 255.359f, -0, 3134.437f, 0, 0, 0, -2, + false); + } else { + RogueLike::Quests::CompleteQuestById(RL_QUEST_HF_TRIAL_A); + uint32_t reward = RogueLike::XP::ConvertLevelToXP(RogueLike::XP::GetCurrentLevel() + 1); + RogueLike::XP::GrantXP(reward); + for (auto& kill : currentTrialActorList) { + Actor_Kill(kill); + kill = NULL; + } + currentTrialActorList.clear(); + GameInteractor::RawAction::SetWeatherStorm(false); + } + break; + case RL_QUEST_HF_TRIAL_B: + if (!CheckQuestGoalCompleteById(RL_QUEST_HF_TRIAL_B)) { + GameInteractor::RawAction::SetWeatherStorm(true); + gSaveContext.timerState = 6; + gSaveContext.timerSeconds = 70; + } else { + RogueLike::Quests::CompleteQuestById(RL_QUEST_HF_TRIAL_B); + uint32_t reward = RogueLike::XP::ConvertLevelToXP(RogueLike::XP::GetCurrentLevel() + 1); + gSaveContext.timerState = 0; + gSaveContext.timerSeconds = 0; + GameInteractor::RawAction::SetWeatherStorm(false); + } + break; + case RL_QUEST_KV_POTHUNT: + if (!CheckQuestGoalCompleteById(RL_QUEST_KV_POTHUNT)) { + if (potHuntAvailability.size() == 0) { + potHuntAvailability = potHuntLocations; + } + u8 potRoll = rand() % potHuntAvailability.size(); + Vec3f spawnPoint = potHuntAvailability[potRoll]; + Actor* potActor = Actor_Spawn(&gPlayState->actorCtx, gPlayState, ACTOR_OBJ_TSUBO, spawnPoint.x, + spawnPoint.y, spawnPoint.z, 0, 0, 0, 256, false); + Actor_SetColorFilter(potActor, 0x1000, 150, 0, 1000); + potHuntAvailability.erase(potHuntAvailability.begin() + potRoll); + sendConditionMessage = true; + SendQuestConditionMessage(RL_QUEST_KV_POTHUNT); + } + break; + case RL_QUEST_KV_STALFOS: + if (!CheckQuestGoalCompleteById(RL_QUEST_KV_STALFOS)) { + float centerX = 2475.3f; + float centerZ = 496.3f; + float radius = 100.0f; + + for (int i = 0; i < GetQuestGoal(RL_QUEST_KV_STALFOS); i++) { + float angle = i * (2 * M_PI / 5); + float spawnX = centerX + radius * cosf(angle); + float spawnZ = centerZ + radius * sinf(angle); + + if (ActorDB::Instance->RetrieveEntry(ACTOR_EN_TEST).entry.valid) { + Actor_Spawn(&gPlayState->actorCtx, gPlayState, ACTOR_EN_TEST, spawnX, -4.7f, spawnZ, 0, 0, 0, 1, + 0); + } + } + } + break; + default: + break; + } +} + +RogueLikeQuest FindTrialByLocation(Actor* trialActor) { + switch (gPlayState->sceneNum) { + case SCENE_HYRULE_FIELD: + if (trialActor->world.pos.x == 335.571f && trialActor->world.pos.z == 2677.854f) { + if (LINK_IS_ADULT && ((EQUIP_FLAG_SWORD_MASTER & gSaveContext.inventory.equipment) || + (EQUIP_FLAG_SWORD_BGS & gSaveContext.inventory.equipment))) { + return RL_QUEST_HF_TRIAL_A; + } else { + SendQuestConditionMessage(RL_QUEST_HF_TRIAL_A); + } + } + if (trialActor->world.pos.x == 1490.550f && trialActor->world.pos.z == 8760.643f) { + if (LINK_IS_ADULT) { + return RL_QUEST_HF_TRIAL_B; + } + } + break; + default: + break; + } + + return RL_QUEST_ID_MAX; +} + +static void InitRogueLikeQuests() { + activeQuests.clear(); + currentTrialActorList.clear(); +} + +static void OnLoadGame() { + activeQuests.clear(); + for (auto& load : gSaveContext.ship.quest.data.rogueLike.quests) { + if (load.questDescription != NULL) { + activeQuests.push_back(load); + } + } + + COND_HOOK(OnPlayerUpdate, IS_ROGUELIKE, []() { + Player* player = GET_PLAYER(gPlayState); + RogueLikeQuest questId = RL_QUEST_ID_MAX; + static bool hopOfFaithStart = false; + static bool trialTimerInit = false; + static uint32_t trialTimer = 0; + + if (trialTimerInit == false) { + trialTimer = gPlayState->gameplayFrames; + trialTimerInit = true; + } + + if (CheckActiveQuestById(RL_QUEST_HF_TRIAL_B) && !CheckQuestGoalCompleteById(RL_QUEST_HF_TRIAL_B)) { + if (gSaveContext.timerState == 6) { + if (gSaveContext.timerSeconds > 0) { + if (trialTimer <= gPlayState->gameplayFrames - 20) { + gSaveContext.timerSeconds--; + trialTimer = gPlayState->gameplayFrames; + } + } else if (gSaveContext.timerSeconds <= 0) { + gSaveContext.health = 4; + gSaveContext.timerState = 0; + gSaveContext.timerSeconds = 0; + RogueLike::Quests::RemoveQuestById(RL_QUEST_HF_TRIAL_B); + sendConditionMessage = true; + SendQuestConditionMessage(RL_QUEST_HF_TRIAL_B); + } + } + } + + if (CheckActiveQuestById(RL_QUEST_KF_HOPOFFAITH) && !CheckQuestGoalCompleteById(RL_QUEST_KF_HOPOFFAITH)) { + bool isHopping = (player->stateFlags2 & PLAYER_STATE2_HOPPING); + if (!hopOfFaithStart && isHopping && player->actor.world.pos.y >= 360.0f) { + hopOfFaithStart = true; + } + if (hopOfFaithStart && !isHopping) { + hopOfFaithStart = false; + if ((player->actor.world.pos.x >= 318.0f && player->actor.world.pos.x <= 418.0f) && + (player->actor.world.pos.z >= -227.6f && player->actor.world.pos.z <= -126.5f)) { + RogueLike::Quests::UpdateQuestProgress(RL_QUEST_KF_HOPOFFAITH); + } + } + } + if (gPlayState->sceneNum == SCENE_HYRULE_FIELD) { + Actor* trialActor = + Actor_FindNearby(gPlayState, &GET_PLAYER(gPlayState)->actor, ACTOR_BG_MJIN, ACTORCAT_BG, 45.0f); + if (trialActor != NULL) { + questId = FindTrialByLocation(trialActor); + if (questId != RL_QUEST_ID_MAX) { + if (!CheckActiveQuestById(questId)) { + RogueLike::Quests::AddQuestById(questId); + StartQuest(questId); + } + } + } + } + }); + + COND_HOOK(OnSceneInit, IS_ROGUELIKE, [](u16 sceneNum) { + if (CheckActiveQuestById(RL_QUEST_HF_TRIAL_B) && gSaveContext.timerState == 6 && + gPlayState->sceneNum == SCENE_GERUDO_VALLEY) { + if (gSaveContext.timerSeconds > 0) { + RogueLike::Quests::UpdateQuestProgress(RL_QUEST_HF_TRIAL_B); + } + } + for (auto& quest : activeQuests) { + RogueLike::Quests::ResetQuestProgress(quest.questId); + } + sendConditionMessage = true; + }); + + COND_HOOK(OnRoomInit, IS_ROGUELIKE, [](u16 roomNum) { + if (gPlayState->sceneNum == SCENE_KOKIRI_FOREST) { + if (CheckActiveQuestById(RL_QUEST_KF_STRONGMAN)) { + if (!CheckQuestCompletedById(RL_QUEST_KF_STRONGMAN)) { + RogueLike::Quests::ResetQuestProgress(RL_QUEST_KF_STRONGMAN); + } + } + } + }); + + COND_HOOK(OnSceneSpawnActors, IS_ROGUELIKE, []() { + if (gPlayState->sceneNum == SCENE_HYRULE_FIELD) { + if (CheckActiveQuestById(RL_QUEST_KV_STALFOS)) { + StartQuest(RL_QUEST_KV_STALFOS); + } + Object_Spawn(&gPlayState->objectCtx, OBJECT_MJIN); + Actor_Spawn(&gPlayState->actorCtx, gPlayState, ACTOR_BG_MJIN, 335.571f, -0.0f, 2677.854f, 0, 0, 0, 1, + false); + Actor_Spawn(&gPlayState->actorCtx, gPlayState, ACTOR_BG_MJIN, 1490.550f, -135.0f, 8760.643f, 0, 0, 0, 1, + false); + } + }); + + COND_HOOK(OnActorKill, IS_ROGUELIKE, [](void* actor) { + Actor* refActor = (Actor*)actor; + + switch (refActor->id) { + case ACTOR_EN_TEST: + if (CheckActiveQuestById(RL_QUEST_KV_STALFOS) && gPlayState->sceneNum == SCENE_HYRULE_FIELD) { + if (!CheckQuestGoalCompleteById(RL_QUEST_KV_STALFOS)) { + RogueLike::Quests::UpdateQuestProgress(RL_QUEST_KV_STALFOS); + } + } + break; + case ACTOR_OBJ_TSUBO: + if (gPlayState->sceneNum == SCENE_ZORAS_DOMAIN) { + if (CheckActiveQuestById(RL_QUEST_ZD_POTTERY)) { + if (!(CheckQuestGoalCompleteById(RL_QUEST_ZD_POTTERY) && + CheckQuestCompletedById(RL_QUEST_ZD_POTTERY))) { + if (GET_PLAYER(gPlayState)->actor.world.pos.y >= 830.0f && + refActor->world.pos.y <= 800.0f) { + RogueLike::Quests::UpdateQuestProgress(RL_QUEST_ZD_POTTERY); + } + } + } + } else if (gPlayState->sceneNum == SCENE_KAKARIKO_VILLAGE) { + if (CheckActiveQuestById(RL_QUEST_KV_POTHUNT) && refActor->params == 256) { + if (!(CheckQuestGoalCompleteById(RL_QUEST_KV_POTHUNT) && + CheckQuestCompletedById(RL_QUEST_KV_POTHUNT))) { + RogueLike::Quests::UpdateQuestProgress(RL_QUEST_KV_POTHUNT); + StartQuest(RL_QUEST_KV_POTHUNT); + } + } + } + break; + case ACTOR_EN_ISHI: + if (refActor->world.pos.x == refActor->home.pos.x && refActor->world.pos.z == refActor->home.pos.z) { + return; + } + if (CheckActiveQuestById(RL_QUEST_KF_STRONGMAN) && gPlayState->sceneNum == SCENE_KOKIRI_FOREST && + gPlayState->roomCtx.curRoom.num == 0) { + if (!CheckQuestGoalCompleteById(RL_QUEST_KF_STRONGMAN)) { + RogueLike::Quests::UpdateQuestProgress(RL_QUEST_KF_STRONGMAN); + } + } + break; + case ACTOR_EN_ZF: + if (gPlayState->sceneNum == SCENE_HYRULE_FIELD) { + if (CheckActiveQuestById(RL_QUEST_HF_TRIAL_A)) { + if (!CheckQuestGoalCompleteById(RL_QUEST_HF_TRIAL_A)) { + RogueLike::Quests::UpdateQuestProgress(RL_QUEST_HF_TRIAL_A); + StartQuest(RL_QUEST_HF_TRIAL_A); + } + } + } + break; + default: + break; + } + }); + + // RL_QUEST_KV_STALFOS + COND_ID_HOOK(OnOpenText, 0x503e, IS_ROGUELIKE, [](u16* textId, bool* loadFromMessageTable) { + auto oldEntry = CustomMessage::LoadVanillaMessageTableEntry(*textId); + std::string endOfMessage = oldEntry.GetEnglish().substr(oldEntry.GetEnglish().size() - 4); + auto messageEntry = CustomMessage("Stalfos have been attacking our Cucco's, please help us!" + endOfMessage); + messageEntry.AutoFormat(); + messageEntry.LoadIntoFont(); + *loadFromMessageTable = false; + }); + + COND_ID_HOOK(OnOpenText, 0x5042, IS_ROGUELIKE, [](u16* textId, bool* loadFromMessageTable) { + if (CheckQuestGoalCompleteById(RL_QUEST_KV_STALFOS) && !CheckQuestCompletedById(RL_QUEST_KV_STALFOS)) { + auto oldEntry = CustomMessage::LoadVanillaMessageTableEntry(*textId); + std::string endOfMessage = oldEntry.GetEnglish().substr(oldEntry.GetEnglish().size() - 2); + auto messageEntry = CustomMessage("Thank you for saving our cucco's!" + endOfMessage); + messageEntry.AutoFormat(); + messageEntry.LoadIntoFont(); + + RogueLike::XP::SpawnXPGroup(GET_PLAYER(gPlayState)->actor.world.pos, 20); + RogueLike::Quests::CompleteQuestById(RL_QUEST_KV_STALFOS); + + *loadFromMessageTable = false; + } + }); + + // RL_QUEST_ZD_POTTERY + COND_ID_HOOK(OnOpenText, 0x4006, IS_ROGUELIKE, [](u16* textId, bool* loadFromMessageTable) { + auto oldEntry = CustomMessage::LoadVanillaMessageTableEntry(*textId); + std::string endOfMessage = oldEntry.GetEnglish().substr(oldEntry.GetEnglish().size() - 2); + auto messageEntry = CustomMessage("Have you ever heard the sound of ceramic shattering against the water? You " + "should give it a try, it's beautiful!" + + endOfMessage); + messageEntry.AutoFormat(); + messageEntry.LoadIntoFont(); + + RogueLike::Quests::AddQuestById(RL_QUEST_ZD_POTTERY); + + *loadFromMessageTable = false; + }); + + COND_ID_HOOK(OnOpenText, 0x4007, IS_ROGUELIKE, [](u16* textId, bool* loadFromMessageTable) { + auto oldEntry = CustomMessage::LoadVanillaMessageTableEntry(*textId); + std::string endOfMessage = oldEntry.GetEnglish().substr(oldEntry.GetEnglish().size() - 2); + auto messageEntry = CustomMessage("" + endOfMessage); + + if (!CheckQuestGoalCompleteById(RL_QUEST_ZD_POTTERY)) { + messageEntry = CustomMessage( + "Need a tip? Take one of the Pots outside of the Shop and toss it over the top of the waterfall." + + endOfMessage); + } else { + messageEntry = CustomMessage( + "The sound, wasn't that exhilarating? There may be a few more pots if you fancy another go." + + endOfMessage); + RogueLike::XP::SpawnXPGroup(GET_PLAYER(gPlayState)->actor.world.pos, 10); + RogueLike::Quests::CompleteQuestById(RL_QUEST_ZD_POTTERY); + } + + messageEntry.AutoFormat(); + messageEntry.LoadIntoFont(); + *loadFromMessageTable = false; + }); + + // RL_QUEST_KF_STRONGMAN + COND_ID_HOOK(OnOpenText, 0x1004, IS_ROGUELIKE, [](u16* textId, bool* loadFromMessageTable) { + auto oldEntry = CustomMessage::LoadVanillaMessageTableEntry(*textId); + std::string endOfMessage = oldEntry.GetEnglish().substr(oldEntry.GetEnglish().size() - 2); + auto messageEntry = CustomMessage("" + endOfMessage); + + if (!CheckQuestGoalCompleteById(RL_QUEST_KF_STRONGMAN)) { + messageEntry = CustomMessage( + "Stupid Mido likes these stupid rocks! Don't just stand there, help me smash them!" + endOfMessage); + if (!CheckActiveQuestById(RL_QUEST_KF_STRONGMAN)) { + RogueLike::Quests::AddQuestById(RL_QUEST_KF_STRONGMAN); + RogueLike::Quests::SetQuestProgress( + RL_QUEST_KF_STRONGMAN, DetermineInitialQuestProgress(RL_QUEST_KF_STRONGMAN, ACTOR_EN_ISHI)); + } + } else { + messageEntry = CustomMessage("Thanks for being one of the good guys! Oh, this one? Don't worry, I'll have " + "it smashed by your 17th birthday." + + endOfMessage); + RogueLike::XP::SpawnXPGroup(GET_PLAYER(gPlayState)->actor.world.pos, 10); + RogueLike::Quests::CompleteQuestById(RL_QUEST_KF_STRONGMAN); + } + + messageEntry.AutoFormat(); + messageEntry.LoadIntoFont(); + *loadFromMessageTable = false; + }); + + // RL_QUEST_KF_HOPOFFAITH + COND_ID_HOOK(OnOpenText, 0x10d7, IS_ROGUELIKE, [](u16* textId, bool* loadFromMessageTable) { + auto oldEntry = CustomMessage::LoadVanillaMessageTableEntry(*textId); + std::string endOfMessage = oldEntry.GetEnglish().substr(oldEntry.GetEnglish().size() - 4); + auto messageEntry = CustomMessage("" + endOfMessage); + + if (!CheckActiveQuestById(RL_QUEST_KF_HOPOFFAITH)) { + messageEntry = + CustomMessage("Wow, you came all the way to see me? Not afraid of heights I see." + endOfMessage); + } else { + Message_ContinueTextbox(gPlayState, 0x10d8); + } + + messageEntry.AutoFormat(); + messageEntry.LoadIntoFont(); + *loadFromMessageTable = false; + }); + + COND_ID_HOOK(OnOpenText, 0x10d8, IS_ROGUELIKE, [](u16* textId, bool* loadFromMessageTable) { + auto oldEntry = CustomMessage::LoadVanillaMessageTableEntry(*textId); + std::string endOfMessage = oldEntry.GetEnglish().substr(oldEntry.GetEnglish().size() - 2); + auto messageEntry = CustomMessage("" + endOfMessage); + + if (!CheckActiveQuestById(RL_QUEST_KF_HOPOFFAITH)) { + messageEntry = + CustomMessage("If we're going to continue meeting like this, let's see what you got!" + endOfMessage); + RogueLike::Quests::AddQuestById(RL_QUEST_KF_HOPOFFAITH); + } else { + if (!CheckQuestGoalCompleteById(RL_QUEST_KF_HOPOFFAITH)) { + messageEntry = CustomMessage("Can't read the Quest List, huh. See that fence above the waterfall? " + "Sidehop off of it and land on the middle platform below." + + endOfMessage); + } else { + messageEntry = + CustomMessage("Hey, nice distance! They say you can unload doors doing that." + endOfMessage); + if (!CheckQuestCompletedById(RL_QUEST_KF_HOPOFFAITH)) { + RogueLike::XP::SpawnXPGroup(GET_PLAYER(gPlayState)->actor.world.pos, 10); + RogueLike::Quests::CompleteQuestById(RL_QUEST_KF_HOPOFFAITH); + } + } + } + + messageEntry.AutoFormat(); + messageEntry.LoadIntoFont(); + *loadFromMessageTable = false; + }); + + // RL_QUEST_KV_POTHUNT + COND_ID_HOOK(OnOpenText, 0x5036, IS_ROGUELIKE, [](u16* textId, bool* loadFromMessageTable) { + auto oldEntry = CustomMessage::LoadVanillaMessageTableEntry(*textId); + std::string endOfMessage = oldEntry.GetEnglish().substr(oldEntry.GetEnglish().size() - 2); + auto messageEntry = CustomMessage("" + endOfMessage); + + if (!CheckActiveQuestById(RL_QUEST_KV_POTHUNT)) { + messageEntry = CustomMessage("I've noticed you have a knack for smashing things. I developed a special Pot " + "that could use testing, it's hidden around here somewhere." + + endOfMessage); + RogueLike::Quests::AddQuestById(RL_QUEST_KV_POTHUNT); + StartQuest(RL_QUEST_KV_POTHUNT); + } else { + if (!CheckQuestGoalCompleteById(RL_QUEST_KV_POTHUNT)) { + messageEntry = CustomMessage("It's hidden around here somewhere, get hunting!" + endOfMessage); + } else { + if (!CheckQuestCompletedById(RL_QUEST_KV_POTHUNT)) { + messageEntry = + CustomMessage("Nicely done! It's not much but here's something for your help." + endOfMessage); + RogueLike::XP::SpawnXPGroup(GET_PLAYER(gPlayState)->actor.world.pos, 10); + RogueLike::Quests::CompleteQuestById(RL_QUEST_KV_POTHUNT); + } else { + messageEntry = CustomMessage("Thanks for your help." + endOfMessage); + } + } + } + + messageEntry.AutoFormat(); + messageEntry.LoadIntoFont(); + *loadFromMessageTable = false; + }); +} + +static RegisterShipInitFunc initFunc(InitRogueLikeQuests, {}); +static RegisterShipInitFunc initFunc2(OnLoadGame, { "IS_ROGUELIKE" }); \ No newline at end of file diff --git a/soh/soh/Enhancements/RogueLike/Quests.h b/soh/soh/Enhancements/RogueLike/Quests.h new file mode 100644 index 00000000000..90fd4b2271c --- /dev/null +++ b/soh/soh/Enhancements/RogueLike/Quests.h @@ -0,0 +1,23 @@ +#ifndef ROGUELIKE_QUESTS_H +#define ROGUELIKE_QUESTS_H + +extern "C" { +#include +} + +namespace RogueLike { + +namespace Quests { + +void CompleteQuestById(u8 questId); +void AddQuestById(u8 questId); +void RemoveQuestById(u8 questId); +void UpdateQuestProgress(u8 questId); +void SetQuestProgress(u8 questId, u16 progress); +void ResetQuestProgress(u8 questId); + +} // namespace Quests + +} // namespace RogueLike + +#endif \ No newline at end of file diff --git a/soh/soh/Enhancements/RogueLike/RogueLike.cpp b/soh/soh/Enhancements/RogueLike/RogueLike.cpp new file mode 100644 index 00000000000..8c8c689d2e8 --- /dev/null +++ b/soh/soh/Enhancements/RogueLike/RogueLike.cpp @@ -0,0 +1,11 @@ +#include "RogueLike.h" +#include "soh/Enhancements/game-interactor/GameInteractor.h" +#include "soh/ShipInit.hpp" + +std::vector RogueLike::requiredRewards; + +// Entry point for the module, run once on game boot +static void InitRogueLike() { +} + +static RegisterShipInitFunc initFunc(InitRogueLike, {}); diff --git a/soh/soh/Enhancements/RogueLike/RogueLike.h b/soh/soh/Enhancements/RogueLike/RogueLike.h new file mode 100644 index 00000000000..6c513b63a32 --- /dev/null +++ b/soh/soh/Enhancements/RogueLike/RogueLike.h @@ -0,0 +1,16 @@ +#ifndef ROGUELIKE_H +#define ROGUELIKE_H + +#include "GUI/GUI.h" +#include "XP.h" +#include "Difficulty.h" +#include "Quests.h" +#include "Types.h" + +extern std::vector activeQuests; + +namespace RogueLike { +extern std::vector requiredRewards; +} // namespace RogueLike + +#endif // ROGUELIKE_H diff --git a/soh/soh/Enhancements/RogueLike/Types.h b/soh/soh/Enhancements/RogueLike/Types.h new file mode 100644 index 00000000000..693a3f38ff7 --- /dev/null +++ b/soh/soh/Enhancements/RogueLike/Types.h @@ -0,0 +1,45 @@ +#ifndef ROGUELIKE_TYPES_H +#define ROGUELIKE_TYPES_H + +#ifdef __cplusplus +extern "C" { +#endif + +typedef enum { + RL_HEALTH, + RL_ATTACK, + RL_DEFENSE, + RL_SPEED, + RL_MAX, +} RoguelikeStats; + +typedef enum { + RL_QUEST_ACTIVE, + RL_QUEST_COMPLETE, +} RogueLikeQuestStatus; + +typedef enum { + RL_QUEST_HF_TRIAL_A, + RL_QUEST_HF_TRIAL_B, + RL_QUEST_KF_HOPOFFAITH, + RL_QUEST_KF_STRONGMAN, + RL_QUEST_KV_POTHUNT, + RL_QUEST_KV_STALFOS, + RL_QUEST_ZD_POTTERY, + RL_QUEST_ID_MAX, +} RogueLikeQuest; + +typedef struct { + u8 questId; + const char* questName; + const char* questDescription; + u8 questStatus; + u16 questProgress; + u16 questGoal; +} RogueLikeQuestObject; + +#ifdef __cplusplus +} +#endif + +#endif // ROGUELIKE_TYPES_H diff --git a/soh/soh/Enhancements/RogueLike/XP.cpp b/soh/soh/Enhancements/RogueLike/XP.cpp new file mode 100644 index 00000000000..042e60a9f72 --- /dev/null +++ b/soh/soh/Enhancements/RogueLike/XP.cpp @@ -0,0 +1,124 @@ +#include "soh/Enhancements/RogueLike/RogueLike.h" +#include "soh/Enhancements/custom-item/CustomItem.h" + +extern "C" { +#include "variables.h" +#include "functions.h" +#include "macros.h" + +extern PlayState* gPlayState; +} + +extern std::shared_ptr mLevelUpWindow; +extern std::map> rogueLikeStatMap; + +#define BASE_XP CVarGetFloat("gRogueLike.BaseXP", 100.0f) +#define GROWTH_RATE CVarGetFloat("gRogueLike.XPGrowthRate", 1.3f) + +float RogueLike::XP::GetProgressToNextLevel() { + u32 currentXP = gSaveContext.ship.quest.data.rogueLike.xp; + + u32 currentLevel = GetCurrentLevel(); + u32 xpForCurrentLevel = ConvertLevelToXP(currentLevel); + u32 xpForNextLevel = ConvertLevelToXP(currentLevel + 1); + + return static_cast(currentXP - xpForCurrentLevel) / static_cast(xpForNextLevel - xpForCurrentLevel); +} + +u32 RogueLike::XP::GetCurrentLevel() { + return RogueLike::XP::ConvertXPToLevel(gSaveContext.ship.quest.data.rogueLike.xp); +} + +u32 RogueLike::XP::ConvertXPToLevel(u32 xp) { + return static_cast(logf((xp * (GROWTH_RATE - 1) / BASE_XP) + 1) / logf(GROWTH_RATE)); +} + +u32 RogueLike::XP::ConvertLevelToXP(u32 level) { + return static_cast(BASE_XP * ((powf(GROWTH_RATE, level) - 1) / (GROWTH_RATE - 1))); +} + +void RogueLike::XP::GrantXP(u32 amount) { + + u32 oldLevel = GetCurrentLevel(); + gSaveContext.ship.quest.data.rogueLike.xp += amount; + RogueLike::Difficulty::IndicateActivity(); + u32 newLevel = GetCurrentLevel(); + + if (newLevel != oldLevel) { + Sfx_PlaySfxCentered(NA_SE_SY_CORRECT_CHIME); + mLevelUpWindow->Show(); + } +} + +void RogueLike::XP::SpawnXPOrb(Vec3f spawnPos, u32 amount, int16_t flags) { + CustomItem::Spawn( + spawnPos.x, spawnPos.y + 10.0f, spawnPos.z, 0, flags, amount, + [](Actor* actor, PlayState* play) { + RogueLike::XP::GrantXP(CUSTOM_ITEM_PARAM); + Sfx_PlaySfxCentered(NA_SE_SY_RUPY_COUNT); + }, + [](Actor* actor, PlayState* play) { + Matrix_Scale(15.0f, 15.0f, 15.0f, MTXMODE_APPLY); + Matrix_Translate(0.0f, -40.0f, 0.0f, MTXMODE_APPLY); + + u32 amount = CUSTOM_ITEM_PARAM; + GetItemDrawID drawId = GID_RUPEE_GREEN; + + if (amount >= 200) { + drawId = GID_RUPEE_GOLD; + } else if (amount >= 50) { + drawId = GID_RUPEE_PURPLE; + } else if (amount >= 20) { + drawId = GID_RUPEE_RED; + } else if (amount >= 5) { + drawId = GID_RUPEE_BLUE; + } + + GetItem_Draw(play, drawId); + + // Slowly move towards the player + Player* player = GET_PLAYER(play); + + // Don't magnet till it hits the ground + if (actor->bgCheckFlags & 1 && !Player_InBlockingCsMode(gPlayState, player)) { + if (actor->xzDistToPlayer < 100.0f) { + s16 targetYaw = Actor_WorldYawTowardActor(actor, &player->actor); + actor->world.rot.y = targetYaw; + + // the further away, the slower it moves + const f32 desiredSpeed = ((actor->xzDistToPlayer - 10.0f) / 90.0f) * (0.01f - 3.0f) + 3.0f; + actor->speedXZ = desiredSpeed; + } + + if (actor->xzDistToPlayer < 10.0f) { + CUSTOM_ITEM_FLAGS |= CustomItem::KILL_ON_TOUCH; + } + } + }); +} + +void RogueLike::XP::SpawnXPGroup(Vec3f spawnPos, u32 amount) { + u32 remainingAmount = amount; + + while (remainingAmount > 0) { + std::vector orbSizes = { 1 }; + + if (remainingAmount >= 200) + orbSizes.push_back(200); + if (remainingAmount >= 50) + orbSizes.push_back(50); + if (remainingAmount >= 20) + orbSizes.push_back(20); + if (remainingAmount >= 5) + orbSizes.push_back(5); + + u32 orbAmount = orbSizes[Rand_ZeroOne() * orbSizes.size()]; + + SpawnXPOrb(spawnPos, orbAmount); + remainingAmount -= orbAmount; + } +} + +void RogueLike::XP::UpdatePlayerStats() { + gSaveContext.healthCapacity = 0x30 + (gSaveContext.ship.quest.data.rogueLike.stats[RL_HEALTH] * 0x10); +} diff --git a/soh/soh/Enhancements/RogueLike/XP.h b/soh/soh/Enhancements/RogueLike/XP.h new file mode 100644 index 00000000000..bfc9013ee3f --- /dev/null +++ b/soh/soh/Enhancements/RogueLike/XP.h @@ -0,0 +1,27 @@ +#ifndef ROGUELIKE_XP_H +#define ROGUELIKE_XP_H + +#include "soh/Enhancements/custom-item/CustomItem.h" + +extern "C" { +#include +} + +namespace RogueLike { + +namespace XP { + +float GetProgressToNextLevel(); +u32 GetCurrentLevel(); +u32 ConvertXPToLevel(u32 xp); +u32 ConvertLevelToXP(u32 level); +void GrantXP(u32 amount); +void SpawnXPOrb(Vec3f spawnPos, u32 amount, int16_t flags = CustomItem::STOP_BOBBING | CustomItem::TOSS_ON_SPAWN); +void SpawnXPGroup(Vec3f spawnPos, u32 amount); +void UpdatePlayerStats(); + +} // namespace XP + +} // namespace RogueLike + +#endif diff --git a/soh/soh/Enhancements/SkipGIAnimations.cpp b/soh/soh/Enhancements/SkipGIAnimations.cpp new file mode 100644 index 00000000000..0b599e11ecf --- /dev/null +++ b/soh/soh/Enhancements/SkipGIAnimations.cpp @@ -0,0 +1,789 @@ +#include "SkipGIAnimations.h" + +#include "functions.h" +#include "macros.h" +#include "variables.h" +#include "soh/Enhancements/randomizer/3drando/random.hpp" +#include "soh/Enhancements/randomizer/context.h" +#include "soh/Enhancements/enhancementTypes.h" +#include "soh/OTRGlobals.h" +#include "soh/cvar_prefixes.h" +#include "soh/ResourceManagerHelpers.h" +#include "soh/Enhancements/item-tables/ItemTableManager.h" + +// -------------------------------------------------------- +// CVar wiring +// -------------------------------------------------------- + +#define SKIPGI_LOG_PICKUP(entry, cat) \ + SPDLOG_INFO("[SkipGI][PICKUP] " \ + "modIndex={} itemId={} getItemId={} (as RG={}, as ItemID=0x{:X}) | " \ + "drawModIndex={} drawItemId={} (as RG={}, as ItemID=0x{:X}) | " \ + "isIceTrap={} classifiedCat={}", \ + entry.modIndex, entry.itemId, entry.getItemId, entry.getItemId, entry.getItemId, entry.drawModIndex, \ + entry.drawItemId, entry.drawItemId, entry.drawItemId, IsIceTrap(entry), (int)cat) + +#define CVAR_SKIP_GI_SIMPLE CVAR_RANDOMIZER_ENHANCEMENT("TimeSavers.SkipGetItemAnimation") + +// Indices into the advanced category arrays +// Order must match the user's requested order. +enum SkipGIAdvancedCategoryIndex { + SKIPGI_CAT_UNKNOWN = -1, + SKIPGI_CAT_MAJOR_ITEMS = 0, + SKIPGI_CAT_LESSER_ITEMS, + SKIPGI_CAT_TRIFORCE, + SKIPGI_CAT_GREG_RUPEE, + SKIPGI_CAT_SONGS, + SKIPGI_CAT_DUNGEON_REWARDS, + SKIPGI_CAT_BOSS_SOULS, + SKIPGI_CAT_BOSS_KEYS, + SKIPGI_CAT_DUNGEON_KEYS, + SKIPGI_CAT_OVERWORLD_KEYS, + SKIPGI_CAT_SKULLTULA_TOKENS, + SKIPGI_CAT_HEART_CONTAINERS, + SKIPGI_CAT_HEART_PIECES, + SKIPGI_CAT_MAPS_COMPASSES, + SKIPGI_CAT_CONSUMABLES, + SKIPGI_CAT_ICE_TRAPS, +}; + +// NOTE: You must update SKIP_GI_ADVANCED_CATEGORY_COUNT to 16 in SkipGIAnimations.h + +// Keep this in sync with the enum above +const char* skipGIAdvancedCVarList[SKIP_GI_ADVANCED_CATEGORY_COUNT] = { + CVAR_RANDOMIZER_ENHANCEMENT("TimeSavers.SkipGetItemAnimationAdvanced.MajorItems"), // 0 + CVAR_RANDOMIZER_ENHANCEMENT("TimeSavers.SkipGetItemAnimationAdvanced.LesserItems"), // 1 + CVAR_RANDOMIZER_ENHANCEMENT("TimeSavers.SkipGetItemAnimationAdvanced.Triforce"), // 2 + CVAR_RANDOMIZER_ENHANCEMENT("TimeSavers.SkipGetItemAnimationAdvanced.GregRupee"), // 3 + CVAR_RANDOMIZER_ENHANCEMENT("TimeSavers.SkipGetItemAnimationAdvanced.Songs"), // 4 + CVAR_RANDOMIZER_ENHANCEMENT("TimeSavers.SkipGetItemAnimationAdvanced.DungeonRewards"), // 5 + CVAR_RANDOMIZER_ENHANCEMENT("TimeSavers.SkipGetItemAnimationAdvanced.BossSouls"), // 6 + CVAR_RANDOMIZER_ENHANCEMENT("TimeSavers.SkipGetItemAnimationAdvanced.BossKeys"), // 7 + CVAR_RANDOMIZER_ENHANCEMENT("TimeSavers.SkipGetItemAnimationAdvanced.DungeonKeys"), // 8 + CVAR_RANDOMIZER_ENHANCEMENT("TimeSavers.SkipGetItemAnimationAdvanced.OverworldKeys"), // 9 + CVAR_RANDOMIZER_ENHANCEMENT("TimeSavers.SkipGetItemAnimationAdvanced.SkulltulaTokens"), // 10 + CVAR_RANDOMIZER_ENHANCEMENT("TimeSavers.SkipGetItemAnimationAdvanced.HeartContainers"), // 11 + CVAR_RANDOMIZER_ENHANCEMENT("TimeSavers.SkipGetItemAnimationAdvanced.HeartPieces"), // 12 + CVAR_RANDOMIZER_ENHANCEMENT("TimeSavers.SkipGetItemAnimationAdvanced.MapsCompasses"), // 13 + CVAR_RANDOMIZER_ENHANCEMENT("TimeSavers.SkipGetItemAnimationAdvanced.Consumables"), // 14 + CVAR_RANDOMIZER_ENHANCEMENT("TimeSavers.SkipGetItemAnimationAdvanced.IceTraps"), // 15 +}; + +const char* skipGIAdvancedNameList[SKIP_GI_ADVANCED_CATEGORY_COUNT] = { + "Major Items", // 0 + "Lesser Items", // 1 + "Triforce Pieces", // 2 + "Greg the Green Rupee", // 3 + "Songs", // 4 + "Dungeon Rewards", // 5 + "Boss Souls", // 6 + "Boss Keys", // 7 + "Dungeon Keys", // 8 + "Overworld Keys", // 9 + "Skulltula Tokens", // 10 + "Heart Containers", // 11 + "Heart Pieces", // 12 + "Maps & Compasses", // 13 + "Consumables", // 14 + "All Ice Traps", // 15 +}; + +// -------------------------------------------------------- +// Helpers +// -------------------------------------------------------- + +static GetItemEntry GetVisualEntry(const GetItemEntry& entry) { + // drawItemId is a uint16_t in GetItemEntry, but ItemTableManager expects GetItemID. + // In practice, vanilla draw ids map to the vanilla GetItem table. + return ItemTableManager::Instance->RetrieveItemEntry(entry.drawModIndex, (GetItemID)entry.drawItemId); +} + +static bool IsRandomizerEntry(const GetItemEntry& entry) { + return entry.modIndex == MOD_RANDOMIZER; +} + +static uint16_t GetEffectiveVanillaItemId(const GetItemEntry& entry) { + // For vanilla gives (modIndex == 0), entry.itemId is the authoritative ItemID in some paths + // (your Sun's Song log proves drawModIndex/drawItemId can be something else entirely). + if (entry.modIndex != MOD_RANDOMIZER) { + // If draw info is also vanilla, prefer it (matches your other items where Item Give matched drawItemId) + if (entry.drawModIndex == 0) { + return entry.drawItemId; + } + // Otherwise fall back to the actual item being given + return (uint16_t)entry.itemId; + } + + // Randomizer entries keep using drawItemId for vanilla-table interpretation + return (uint16_t)entry.drawItemId; +} + +// ---------- Specific RG/ITEM check helpers ---------- + +static bool IsFirstMajorProgressive(const GetItemEntry& entry) { + if (!IsRandomizerEntry(entry)) { + return false; + } + + switch (entry.getItemId) { + case RG_PROGRESSIVE_BOMB_BAG: + return CUR_UPG_VALUE(UPG_BOMB_BAG) == 0; + case RG_PROGRESSIVE_BOW: + return INV_CONTENT(ITEM_BOW) == ITEM_NONE; + case RG_PROGRESSIVE_SLINGSHOT: + return INV_CONTENT(ITEM_SLINGSHOT) == ITEM_NONE; + case RG_PROGRESSIVE_NUT_UPGRADE: + return CUR_UPG_VALUE(UPG_NUTS) == 0; + case RG_PROGRESSIVE_STICK_UPGRADE: + return CUR_UPG_VALUE(UPG_STICKS) == 0; + case RG_PROGRESSIVE_MAGIC_METER: + return gSaveContext.magicLevel == 0; + case RG_PROGRESSIVE_OCARINA: + return INV_CONTENT(ITEM_OCARINA_FAIRY) == ITEM_NONE; + case RG_PROGRESSIVE_BOMBCHU_BAG: + return INV_CONTENT(ITEM_BOMBCHU) == ITEM_NONE; + default: + return false; + } +} + +static bool IsMajorItem(const GetItemEntry& entry) { + if (IsRandomizerEntry(entry)) { + if (entry.getItemId == RG_LONGSHOT) { + return true; + } + } else { + if ((int)GetEffectiveVanillaItemId(entry) == ITEM_LONGSHOT) { + return true; + } + } + + if (IsRandomizerEntry(entry)) { + switch (entry.getItemId) { + case RG_PROGRESSIVE_BOMB_BAG: + case RG_PROGRESSIVE_BOW: + case RG_PROGRESSIVE_SLINGSHOT: + case RG_PROGRESSIVE_NUT_UPGRADE: + case RG_PROGRESSIVE_STICK_UPGRADE: + case RG_PROGRESSIVE_MAGIC_METER: + case RG_PROGRESSIVE_OCARINA: + case RG_PROGRESSIVE_BOMBCHU_BAG: + return IsFirstMajorProgressive(entry); + default: + break; + } + + switch (entry.getItemId) { + case RG_KOKIRI_SWORD: + case RG_MASTER_SWORD: + case RG_GIANTS_KNIFE: + case RG_BIGGORON_SWORD: + + case RG_MIRROR_SHIELD: + + case RG_IRON_BOOTS: + case RG_HOVER_BOOTS: + + case RG_BOOMERANG: + case RG_LENS_OF_TRUTH: + case RG_MEGATON_HAMMER: + + case RG_DINS_FIRE: + case RG_FARORES_WIND: + case RG_NAYRUS_LOVE: + + case RG_FIRE_ARROWS: + case RG_ICE_ARROWS: + case RG_LIGHT_ARROWS: + + case RG_GERUDO_MEMBERSHIP_CARD: + case RG_MAGIC_BEAN_PACK: + + case RG_WEIRD_EGG: + case RG_ZELDAS_LETTER: + case RG_POCKET_EGG: + case RG_COJIRO: + case RG_ODD_MUSHROOM: + case RG_ODD_POTION: + case RG_POACHERS_SAW: + case RG_BROKEN_SWORD: + case RG_PRESCRIPTION: + case RG_EYEBALL_FROG: + case RG_EYEDROPS: + case RG_CLAIM_CHECK: + + case RG_PROGRESSIVE_HOOKSHOT: + case RG_PROGRESSIVE_STRENGTH: + case RG_PROGRESSIVE_WALLET: + case RG_PROGRESSIVE_SCALE: + case RG_PROGRESSIVE_GORONSWORD: + + case RG_MAGIC_SINGLE: + + case RG_EMPTY_BOTTLE: + case RG_BOTTLE_WITH_MILK: + case RG_BOTTLE_WITH_RED_POTION: + case RG_BOTTLE_WITH_GREEN_POTION: + case RG_BOTTLE_WITH_BLUE_POTION: + case RG_BOTTLE_WITH_FAIRY: + case RG_BOTTLE_WITH_FISH: + case RG_BOTTLE_WITH_BLUE_FIRE: + case RG_BOTTLE_WITH_BUGS: + case RG_BOTTLE_WITH_POE: + case RG_RUTOS_LETTER: + case RG_BOTTLE_WITH_BIG_POE: + + case RG_FAIRY_OCARINA: + + case RG_BOMB_BAG: + + case RG_FAIRY_BOW: + + case RG_FAIRY_SLINGSHOT: + + case RG_GORONS_BRACELET: + case RG_SILVER_GAUNTLETS: + case RG_GOLDEN_GAUNTLETS: + + case RG_SILVER_SCALE: + case RG_GOLDEN_SCALE: + case RG_BRONZE_SCALE: + + case RG_ADULT_WALLET: + case RG_GIANT_WALLET: + case RG_TYCOON_WALLET: + case RG_CHILD_WALLET: + + case RG_DEKU_STICK_BAG: + case RG_DEKU_NUT_BAG: + + case RG_HOOKSHOT: + + case RG_SKELETON_KEY: + case RG_FISHING_POLE: + case RG_ARCHIPELAGO_ITEM_PROGRESSIVE: + + case RG_OCARINA_A_BUTTON: + case RG_OCARINA_C_UP_BUTTON: + case RG_OCARINA_C_DOWN_BUTTON: + case RG_OCARINA_C_LEFT_BUTTON: + case RG_OCARINA_C_RIGHT_BUTTON: + + case RG_ABILITY_ISG: + case RG_ABILITY_OI: + case RG_ABILITY_QPA: + case RG_ABILITY_HESS: + case RG_ABILITY_SUPERSLIDE: + case RG_ABILITY_HOVER: + case RG_ABILITY_EQUIP_SWAP: + case RG_ABILITY_GROUND_JUMP: + case RG_ABILITY_WEIRDSHOT: + return true; + + default: + return false; + } + } + + const int id = (int)GetEffectiveVanillaItemId(entry); + switch (id) { + case ITEM_BOW: + case ITEM_ARROW_FIRE: + case ITEM_ARROW_ICE: + case ITEM_ARROW_LIGHT: + case ITEM_DINS_FIRE: + case ITEM_FARORES_WIND: + case ITEM_NAYRUS_LOVE: + case ITEM_SLINGSHOT: + case ITEM_HOOKSHOT: + case ITEM_BOOMERANG: + case ITEM_LENS: + case ITEM_BEAN: + case ITEM_HAMMER: + + case ITEM_OCARINA_FAIRY: + + case ITEM_SWORD_KOKIRI: + case ITEM_SWORD_MASTER: + case ITEM_SWORD_BGS: + case ITEM_SWORD_KNIFE: + + case ITEM_SHIELD_MIRROR: + + case ITEM_BOOTS_IRON: + case ITEM_BOOTS_HOVER: + + case ITEM_BOMB_BAG_20: + + case ITEM_BULLET_BAG_30: + + case ITEM_BRACELET: + case ITEM_GAUNTLETS_SILVER: + case ITEM_GAUNTLETS_GOLD: + + case ITEM_SCALE_SILVER: + case ITEM_SCALE_GOLDEN: + + case ITEM_WALLET_ADULT: + case ITEM_WALLET_GIANT: + + case ITEM_GERUDO_CARD: + + case ITEM_SINGLE_MAGIC: + + case ITEM_POCKET_EGG: + case ITEM_COJIRO: + case ITEM_ODD_MUSHROOM: + case ITEM_ODD_POTION: + case ITEM_SAW: + case ITEM_SWORD_BROKEN: + case ITEM_PRESCRIPTION: + case ITEM_FROG: + case ITEM_EYEDROPS: + case ITEM_CLAIM_CHECK: + + case ITEM_BOTTLE: + case ITEM_POTION_RED: + case ITEM_POTION_GREEN: + case ITEM_POTION_BLUE: + case ITEM_FAIRY: + case ITEM_FISH: + case ITEM_BLUE_FIRE: + case ITEM_POE: + case ITEM_MILK_BOTTLE: + case ITEM_LETTER_RUTO: + case ITEM_BIG_POE: + case ITEM_BUG: + return true; + + default: + return false; + } +} + +static bool IsLesserItem(const GetItemEntry& entry) { + if (IsRandomizerEntry(entry)) { + switch (entry.getItemId) { + case RG_PROGRESSIVE_BOMB_BAG: + case RG_PROGRESSIVE_BOW: + case RG_PROGRESSIVE_SLINGSHOT: + case RG_PROGRESSIVE_NUT_UPGRADE: + case RG_PROGRESSIVE_STICK_UPGRADE: + case RG_PROGRESSIVE_MAGIC_METER: + case RG_PROGRESSIVE_OCARINA: + case RG_PROGRESSIVE_BOMBCHU_BAG: + return !IsFirstMajorProgressive(entry); + default: + break; + } + + switch (entry.getItemId) { + case RG_GORON_TUNIC: + case RG_ZORA_TUNIC: + case RG_DEKU_SHIELD: + case RG_HYLIAN_SHIELD: + case RG_STONE_OF_AGONY: + case RG_DOUBLE_DEFENSE: + case RG_MAGIC_BEAN: + + case RG_BIG_QUIVER: + case RG_BIGGEST_QUIVER: + + case RG_BIG_BOMB_BAG: + case RG_BIGGEST_BOMB_BAG: + + case RG_BIG_BULLET_BAG: + case RG_BIGGEST_BULLET_BAG: + + case RG_DEKU_NUT_CAPACITY_30: + case RG_DEKU_NUT_CAPACITY_40: + case RG_DEKU_STICK_CAPACITY_20: + case RG_DEKU_STICK_CAPACITY_30: + + case RG_MAGIC_DOUBLE: + + case RG_QUIVER_INF: + case RG_BOMB_BAG_INF: + case RG_BULLET_BAG_INF: + case RG_STICK_UPGRADE_INF: + case RG_NUT_UPGRADE_INF: + case RG_MAGIC_INF: + case RG_BOMBCHU_INF: + case RG_WALLET_INF: + + case RG_ARCHIPELAGO_ITEM_USEFUL: + return true; + + default: + return false; + } + } + + const int id = (int)GetEffectiveVanillaItemId(entry); + switch (id) { + case ITEM_TUNIC_GORON: + case ITEM_TUNIC_ZORA: + case ITEM_SHIELD_DEKU: + case ITEM_SHIELD_HYLIAN: + case ITEM_STONE_OF_AGONY: + case ITEM_DOUBLE_DEFENSE: + + case ITEM_OCARINA_TIME: + + case ITEM_QUIVER_30: + case ITEM_QUIVER_40: + case ITEM_QUIVER_50: + + case ITEM_BOMB_BAG_30: + case ITEM_BOMB_BAG_40: + + case ITEM_BULLET_BAG_40: + case ITEM_BULLET_BAG_50: + + case ITEM_NUT_UPGRADE_30: + case ITEM_NUT_UPGRADE_40: + case ITEM_STICK_UPGRADE_20: + case ITEM_STICK_UPGRADE_30: + + case ITEM_DOUBLE_MAGIC: + return true; + + default: + return false; + } +} + +static bool IsTriforce(const GetItemEntry& entry) { + // No vanilla ItemID triforce exists in the enum you pasted. + return IsRandomizerEntry(entry) && entry.getItemId == RG_TRIFORCE_PIECE; +} + +static bool IsGregRupee(const GetItemEntry& entry) { + // No vanilla ItemID greg exists in the enum you pasted. + return IsRandomizerEntry(entry) && entry.getItemId == RG_GREG_RUPEE; +} + +static bool IsSong(const GetItemEntry& entry) { + // Randomizer (RG) + if (IsRandomizerEntry(entry)) { + switch (entry.getItemId) { + case RG_ZELDAS_LULLABY: + case RG_EPONAS_SONG: + case RG_SARIAS_SONG: + case RG_SUNS_SONG: + case RG_SONG_OF_TIME: + case RG_SONG_OF_STORMS: + case RG_MINUET_OF_FOREST: + case RG_BOLERO_OF_FIRE: + case RG_SERENADE_OF_WATER: + case RG_REQUIEM_OF_SPIRIT: + case RG_NOCTURNE_OF_SHADOW: + case RG_PRELUDE_OF_LIGHT: + return true; + default: + break; + } + } + + // Vanilla (ItemID) MUST use drawItemId per your logs. + const int id = (int)GetEffectiveVanillaItemId(entry); + switch (id) { + case ITEM_SONG_MINUET: + case ITEM_SONG_BOLERO: + case ITEM_SONG_SERENADE: + case ITEM_SONG_REQUIEM: + case ITEM_SONG_NOCTURNE: + case ITEM_SONG_PRELUDE: + case ITEM_SONG_LULLABY: + case ITEM_SONG_EPONA: + case ITEM_SONG_SARIA: + case ITEM_SONG_SUN: + case ITEM_SONG_TIME: + case ITEM_SONG_STORMS: + return true; + default: + return false; + } +} + +static bool IsDungeonReward(const GetItemEntry& entry) { + // Randomizer (RG) + if (IsRandomizerEntry(entry)) { + switch (entry.getItemId) { + case RG_KOKIRI_EMERALD: + case RG_GORON_RUBY: + case RG_ZORA_SAPPHIRE: + case RG_FOREST_MEDALLION: + case RG_FIRE_MEDALLION: + case RG_WATER_MEDALLION: + case RG_SPIRIT_MEDALLION: + case RG_SHADOW_MEDALLION: + case RG_LIGHT_MEDALLION: + return true; + default: + break; + } + } + + // Vanilla (ItemID) MUST use drawItemId per your logs. + const int id = (int)GetEffectiveVanillaItemId(entry); + switch (id) { + case ITEM_KOKIRI_EMERALD: + case ITEM_GORON_RUBY: + case ITEM_ZORA_SAPPHIRE: + case ITEM_MEDALLION_FOREST: + case ITEM_MEDALLION_FIRE: + case ITEM_MEDALLION_WATER: + case ITEM_MEDALLION_SPIRIT: + case ITEM_MEDALLION_SHADOW: + case ITEM_MEDALLION_LIGHT: + return true; + default: + return false; + } +} + +static bool IsBossSoul(const GetItemEntry& entry) { + // No vanilla ItemID for boss souls in the enum you pasted. + return IsRandomizerEntry(entry) && (entry.getItemId >= RG_GOHMA_SOUL && entry.getItemId <= RG_GANON_SOUL); +} + +static bool IsBossKey(const GetItemEntry& entry) { + // Randomizer (RG range) + if (IsRandomizerEntry(entry)) { + return (entry.getItemId >= RG_FOREST_TEMPLE_BOSS_KEY && entry.getItemId <= RG_GANONS_CASTLE_BOSS_KEY); + } + + // Vanilla (ItemID) MUST use drawItemId per your logs. + return (int)GetEffectiveVanillaItemId(entry) == ITEM_KEY_BOSS; +} + +static bool IsDungeonKey(const GetItemEntry& entry) { + // Randomizer (RG range) + if (IsRandomizerEntry(entry)) { + return (entry.getItemId >= RG_FOREST_TEMPLE_SMALL_KEY && entry.getItemId <= RG_TREASURE_GAME_KEY_RING); + } + + // Vanilla (ItemID) MUST use drawItemId per your logs. + return (int)GetEffectiveVanillaItemId(entry) == ITEM_KEY_SMALL; +} + +static bool IsOverworldKey(const GetItemEntry& entry) { + // No vanilla ItemID overworld keys exist in the enum you pasted. + return IsRandomizerEntry(entry) && + (entry.getItemId >= RG_GUARD_HOUSE_KEY && entry.getItemId <= RG_FISHING_HOLE_KEY); +} + +static bool IsSkulltulaToken(const GetItemEntry& entry) { + // Randomizer (RG) + if (IsRandomizerEntry(entry) && entry.getItemId == RG_GOLD_SKULLTULA_TOKEN) { + return true; + } + // Vanilla (ItemID) MUST use drawItemId per your logs. + return (int)GetEffectiveVanillaItemId(entry) == ITEM_SKULL_TOKEN; +} + +static bool IsHeartContainer(const GetItemEntry& entry) { + // Randomizer (RG) + if (IsRandomizerEntry(entry) && entry.getItemId == RG_HEART_CONTAINER) { + return true; + } + // Vanilla (ItemID) MUST use drawItemId per your logs. + return (int)GetEffectiveVanillaItemId(entry) == ITEM_HEART_CONTAINER; +} + +static bool IsHeartPiece(const GetItemEntry& entry) { + // Randomizer (RG) + if (IsRandomizerEntry(entry)) { + switch (entry.getItemId) { + case RG_PIECE_OF_HEART: + case RG_TREASURE_GAME_HEART: + return true; + default: + break; + } + } + + // Vanilla (ItemID) MUST use drawItemId per your logs. + switch ((int)GetEffectiveVanillaItemId(entry)) { + case ITEM_HEART_PIECE: + case ITEM_HEART_PIECE_2: + return true; + default: + return false; + } +} + +static bool IsMapOrCompass(const GetItemEntry& entry) { + // Randomizer (RG) + if (IsRandomizerEntry(entry)) { + // Maps + if (entry.getItemId >= RG_DEKU_TREE_MAP && entry.getItemId <= RG_ICE_CAVERN_MAP) { + return true; + } + // Compasses + if (entry.getItemId >= RG_DEKU_TREE_COMPASS && entry.getItemId <= RG_ICE_CAVERN_COMPASS) { + return true; + } + return false; + } + + // Vanilla (ItemID) MUST use drawItemId per your logs. + switch ((int)GetEffectiveVanillaItemId(entry)) { + case ITEM_DUNGEON_MAP: + case ITEM_COMPASS: + return true; + default: + return false; + } +} + +// Consumables: rupees, refills, shop trash etc. +// For randomizer entries we treat ITEM_CATEGORY_JUNK as consumables. +static bool IsConsumable(const GetItemEntry& entry) { + // Randomizer-specific consumables + if (IsRandomizerEntry(entry)) { + return entry.getItemCategory == ITEM_CATEGORY_JUNK || entry.getItemId == RG_HUGE_RUPEE || + entry.getItemId == RG_ARCHIPELAGO_ITEM_JUNK; + } + + // Vanilla consumables (ItemID) MUST use drawItemId per your logs. + switch ((int)GetEffectiveVanillaItemId(entry)) { + case ITEM_MAGIC_SMALL: + case ITEM_MAGIC_LARGE: + case ITEM_HEART: + case ITEM_RUPEE_GREEN: + case ITEM_RUPEE_BLUE: + case ITEM_RUPEE_RED: + case ITEM_RUPEE_PURPLE: + case ITEM_RUPEE_GOLD: + case ITEM_STICKS_5: + case ITEM_STICKS_10: + case ITEM_NUTS_5: + case ITEM_NUTS_10: + case ITEM_BOMBS_5: + case ITEM_BOMBS_10: + case ITEM_BOMBS_20: + case ITEM_BOMBS_30: + case ITEM_ARROWS_SMALL: + case ITEM_ARROWS_MEDIUM: + case ITEM_ARROWS_LARGE: + case ITEM_SEEDS_30: + case ITEM_BOMBCHUS_5: + case ITEM_BOMBCHUS_20: + case ITEM_MILK: + return true; + default: + break; + } + + // Also keep the vanilla junk category behavior you already had (covers shop trash etc.) + return entry.getItemCategory == ITEM_CATEGORY_JUNK; +} + +static bool IsIceTrap(const GetItemEntry& entry) { + // Only RG exists here in your current setup (no vanilla ITEM_ICE_TRAP in the enum you pasted). + return IsRandomizerEntry(entry) && entry.getItemId == RG_ICE_TRAP; +} + +// -------------------------------------------------------- +// Category classifier (one and only place that decides +// which bucket an entry belongs to). +// -------------------------------------------------------- + +static SkipGIAdvancedCategoryIndex ClassifyAdvancedCategory(const GetItemEntry& entry) { + // Ice traps always go to ice trap bucket + if (IsIceTrap(entry)) { + return SKIPGI_CAT_ICE_TRAPS; + } + + // IMPORTANT: special categories that are visually distinct should be checked before consumables, + // since some tables may mark them with junk-ish categories. + if (IsSkulltulaToken(entry)) + return SKIPGI_CAT_SKULLTULA_TOKENS; + if (IsHeartContainer(entry)) + return SKIPGI_CAT_HEART_CONTAINERS; + if (IsHeartPiece(entry)) + return SKIPGI_CAT_HEART_PIECES; + if (IsMapOrCompass(entry)) + return SKIPGI_CAT_MAPS_COMPASSES; + + // Consumables (vanilla OR randomizer) + if (IsConsumable(entry)) { + return SKIPGI_CAT_CONSUMABLES; + } + + if (IsBossKey(entry)) + return SKIPGI_CAT_BOSS_KEYS; + if (IsDungeonKey(entry)) + return SKIPGI_CAT_DUNGEON_KEYS; + if (IsSong(entry)) + return SKIPGI_CAT_SONGS; + if (IsDungeonReward(entry)) + return SKIPGI_CAT_DUNGEON_REWARDS; + + // Randomizer-only buckets + if (IsTriforce(entry)) + return SKIPGI_CAT_TRIFORCE; + if (IsGregRupee(entry)) + return SKIPGI_CAT_GREG_RUPEE; + if (IsBossSoul(entry)) + return SKIPGI_CAT_BOSS_SOULS; + if (IsOverworldKey(entry)) + return SKIPGI_CAT_OVERWORLD_KEYS; + + // Major/Lesser + if (IsMajorItem(entry)) + return SKIPGI_CAT_MAJOR_ITEMS; + if (IsLesserItem(entry)) + return SKIPGI_CAT_LESSER_ITEMS; + + // If not classified yet, it's unknown + return SKIPGI_CAT_UNKNOWN; +} + +// -------------------------------------------------------- +// Advanced logic (used when Skip mode == SGIA_ADVANCED) +// -------------------------------------------------------- + +bool ShouldSkipGetItemAnimationAdvanced(const GetItemEntry& entry) { + + // If the base feature is off completely, never skip anything. + if (!CVarGetInteger(CVAR_SKIP_GI_SIMPLE, 0)) { + return false; + } + + const SkipGIAdvancedCategoryIndex cat = ClassifyAdvancedCategory(entry); + + { + const GetItemEntry vis = GetVisualEntry(entry); + SPDLOG_INFO("[SkipGI][DIAG] " + "ENTRY: modIndex={} getItemId={} drawModIndex={} drawItemId={} cat={} | " + "VIS: modIndex={} getItemId={} drawModIndex={} drawItemId={} visCat={}", + entry.modIndex, entry.getItemId, entry.drawModIndex, entry.drawItemId, (int)cat, vis.modIndex, + vis.getItemId, vis.drawModIndex, vis.drawItemId, (int)ClassifyAdvancedCategory(vis)); + } + + // LOG EVERYTHING BEFORE DECISION + SKIPGI_LOG_PICKUP(entry, cat); + + if (cat == SKIPGI_CAT_UNKNOWN) { + return false; // unclassified items never skip + } + + // Ice traps ALWAYS skip (current behavior unchanged) + if (IsIceTrap(entry)) { + return true; + } + + if (cat < 0 || cat >= SKIP_GI_ADVANCED_CATEGORY_COUNT) { + return false; + } + + const char* cvarName = skipGIAdvancedCVarList[cat]; + if (!cvarName) { + return false; + } + + return CVarGetInteger(cvarName, 0) != 0; +} diff --git a/soh/soh/Enhancements/SkipGIAnimations.h b/soh/soh/Enhancements/SkipGIAnimations.h new file mode 100644 index 00000000000..3ff9fc7adfa --- /dev/null +++ b/soh/soh/Enhancements/SkipGIAnimations.h @@ -0,0 +1,21 @@ +#pragma once + +#include "item-tables/ItemTableTypes.h" // for GetItemEntry +#include "soh/Enhancements/randomizer/randomizerTypes.h" + +// Number of advanced categories we want +#define SKIP_GI_ADVANCED_CATEGORY_COUNT 16 + +// CVar names (TimeSavers.SkipGetItemAnimationAdvanced.*) +extern const char* skipGIAdvancedCVarList[SKIP_GI_ADVANCED_CATEGORY_COUNT]; + +// Text shown in the menu ("Skip GI: Items", etc.) +extern const char* skipGIAdvancedNameList[SKIP_GI_ADVANCED_CATEGORY_COUNT]; + +#ifdef __cplusplus +// Advanced-mode logic: only used from C++ code +bool ShouldSkipGetItemAnimationAdvanced(const GetItemEntry& entry); + +RandomizerCheck GetLastIceTrapCheck(); + +#endif diff --git a/soh/soh/Enhancements/TimeDisplay/TimeDisplay.cpp b/soh/soh/Enhancements/TimeDisplay/TimeDisplay.cpp index 8f1f0ac4021..6e7d133e549 100644 --- a/soh/soh/Enhancements/TimeDisplay/TimeDisplay.cpp +++ b/soh/soh/Enhancements/TimeDisplay/TimeDisplay.cpp @@ -1,5 +1,6 @@ #include "TimeDisplay.h" #include "soh/Enhancements/gameplaystats.h" +#include "soh/Enhancements/Holiday/Fredomato.h" #include #include "assets/textures/parameter_static/parameter_static.h" @@ -14,7 +15,9 @@ extern PlayState* gPlayState; uint64_t GetUnixTimestamp(); } -float fontScale = 1.0f; +static bool separateGameplay = false; +static float gameplayFontScale = 1.0f; +static float fontScale = 1.0f; std::string timeDisplayTime = ""; ImTextureID textureDisplay = 0; ImVec4 windowBG = ImVec4(0, 0, 0, 0.5f); @@ -40,10 +43,12 @@ const std::vector timeDisplayList = { { DISPLAY_IN_GAME_TIMER, "Display Gameplay Timer", CVAR_TIME_DISPLAY("Timers.InGameTimer") }, { DISPLAY_TIME_OF_DAY, "Display Time of Day", CVAR_TIME_DISPLAY("Timers.TimeofDay") }, { DISPLAY_CONDITIONAL_TIMER, "Display Conditional Timer", CVAR_TIME_DISPLAY("Timers.HotWater") }, - { DISPLAY_NAVI_TIMER, "Display Navi Timer", CVAR_TIME_DISPLAY("Timers.NaviTimer") } + { DISPLAY_NAVI_TIMER, "Display Navi Timer", CVAR_TIME_DISPLAY("Timers.NaviTimer") }, + { DISPLAY_FRED_QUEST, "Display Fred's Quest", CVAR_TIME_DISPLAY("Timers.FredsQuest") } }; static std::vector activeTimers; +static TimeObject gameplayTimer = timeDisplayList[DISPLAY_IN_GAME_TIMER]; std::string convertDayTime(uint32_t dayTime) { uint32_t totalSeconds = 24 * 60 * 60; @@ -87,6 +92,7 @@ static void TimeDisplayGetTimer(uint32_t timeID) { case DISPLAY_IN_GAME_TIMER: textureDisplay = Ship::Context::GetInstance()->GetWindow()->GetGui()->GetTextureByName("GAMEPLAY_TIMER"); timeDisplayTime = formatTimeDisplay(GAMEPLAYSTAT_TOTAL_TIME).c_str(); + textColor = gSaveContext.ship.stats.gameComplete ? COLOR_LIGHT_GREEN : COLOR_WHITE; break; case DISPLAY_TIME_OF_DAY: if (gSaveContext.dayTime >= DAY_BEGINS && gSaveContext.dayTime < NIGHT_BEGINS) { @@ -134,6 +140,11 @@ static void TimeDisplayGetTimer(uint32_t timeID) { } textureDisplay = Ship::Context::GetInstance()->GetWindow()->GetGui()->GetTextureByName("NAVI_TIMER"); break; + case DISPLAY_FRED_QUEST: + timeDisplayTime = std::to_string(FredsQuestWoodOnHand) + "/" + std::to_string(FredsQuestWoodCollected) + + "/" + std::to_string(CVarGetInteger("gHoliday.Fredomato.FredsQuest.WoodNeeded", 300)); + textureDisplay = Ship::Context::GetInstance()->GetWindow()->GetGui()->GetTextureByName("ITEM_STICK"); + break; default: break; } @@ -146,19 +157,50 @@ void TimeDisplayUpdateDisplayOptions() { activeTimers.push_back(timer); } } +} + +void DrawStandaloneGameplayTimer() { + ImGui::Begin("GameplayTimer", nullptr, + ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoNav | ImGuiWindowFlags_NoFocusOnAppearing | + ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoDocking | ImGuiWindowFlags_NoTitleBar | + ImGuiWindowFlags_NoScrollWithMouse | ImGuiWindowFlags_NoScrollbar); + ImGui::SetWindowFontScale(gameplayFontScale); + ImGui::BeginTable("Gameplay Timer", 2, ImGuiTableFlags_NoClip); + ImGui::PushID(DISPLAY_IN_GAME_TIMER); + TimeDisplayGetTimer(DISPLAY_IN_GAME_TIMER); + ImGui::TableNextColumn(); + ImGui::Image(textureDisplay, ImVec2(16.0f * gameplayFontScale, 16.0f * gameplayFontScale)); + ImGui::TableNextColumn(); + if (timeDisplayTime != "-:--") { + char* textToDecode = new char[timeDisplayTime.size() + 1]; + textToDecode = std::strcpy(textToDecode, timeDisplayTime.c_str()); + size_t textLength = timeDisplayTime.length(); + uint16_t textureIndex = 0; - // if (pushBack) { - // activeTimers.push_back(timeDisplayList[timeID]); - // } else { - // uint32_t index = 0; - // for (auto& check : activeTimers) { - // if (check.timeID == timeID) { - // activeTimers.erase(activeTimers.begin() + index); - // return; - // } - // index++; - // } - // } + for (size_t i = 0; i < textLength; i++) { + if (textToDecode[i] == ':' || textToDecode[i] == '.') { + textureIndex = 10; + } else { + textureIndex = textToDecode[i] - '0'; + } + if (textToDecode[i] == '.') { + ImGui::SetCursorPosY(ImGui::GetCursorPosY() + (8.0f * gameplayFontScale)); + ImGui::Image(Ship::Context::GetInstance()->GetWindow()->GetGui()->GetTextureByName( + digitList[textureIndex].first), + ImVec2(8.0f * gameplayFontScale, 8.0f * gameplayFontScale), ImVec2(0, 0.5f), ImVec2(1, 1), + textColor, ImVec4(0, 0, 0, 0)); + } else { + ImGui::Image(Ship::Context::GetInstance()->GetWindow()->GetGui()->GetTextureByName( + digitList[textureIndex].first), + ImVec2(8.0f * gameplayFontScale, 16.0f * gameplayFontScale), ImVec2(0, 0), ImVec2(1, 1), + textColor, ImVec4(0, 0, 0, 0)); + } + ImGui::SameLine(0, 0); + } + } + ImGui::PopID(); + ImGui::EndTable(); + ImGui::End(); } void TimeDisplayWindow::Draw() { @@ -173,6 +215,10 @@ void TimeDisplayWindow::Draw() { ImGui::PushStyleColor(ImGuiCol_Border, ImVec4(0, 0, 0, 0)); ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 4.0f); + if (separateGameplay) { + DrawStandaloneGameplayTimer(); + } + ImGui::Begin("TimerDisplay", nullptr, ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoNav | ImGuiWindowFlags_NoFocusOnAppearing | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoDocking | ImGuiWindowFlags_NoTitleBar | @@ -183,12 +229,22 @@ void TimeDisplayWindow::Draw() { } else { ImGui::BeginTable("Timer List", 2, ImGuiTableFlags_NoClip); for (auto& timers : activeTimers) { + if (separateGameplay && timers.timeID == DISPLAY_IN_GAME_TIMER) { + continue; + } + ImGui::PushID(timers.timeID); TimeDisplayGetTimer(timers.timeID); ImGui::TableNextColumn(); ImGui::Image(textureDisplay, ImVec2(16.0f * fontScale, 16.0f * fontScale)); ImGui::TableNextColumn(); + if (timers.timeID == DISPLAY_FRED_QUEST) { + ImGui::Text("%s", timeDisplayTime.c_str()); + ImGui::PopID(); + continue; + } + if (timeDisplayTime != "-:--") { char* textToDecode = new char[timeDisplayTime.size() + 1]; textToDecode = std::strcpy(textToDecode, timeDisplayTime.c_str()); @@ -227,6 +283,8 @@ void TimeDisplayWindow::Draw() { } void TimeDisplayInitSettings() { + separateGameplay = CVarGetInteger(CVAR_TIME_DISPLAY("SeparateGameplay"), 0); + gameplayFontScale = CVarGetFloat(CVAR_TIME_DISPLAY("GameplayFontScale"), 1.0f); fontScale = CVarGetFloat(CVAR_TIME_DISPLAY("FontScale"), 1.0f); if (fontScale < 1.0f) { fontScale = 1.0f; diff --git a/soh/soh/Enhancements/TimeDisplay/TimeDisplay.h b/soh/soh/Enhancements/TimeDisplay/TimeDisplay.h index c6635b5a77b..cf1c71c1591 100644 --- a/soh/soh/Enhancements/TimeDisplay/TimeDisplay.h +++ b/soh/soh/Enhancements/TimeDisplay/TimeDisplay.h @@ -18,6 +18,7 @@ typedef enum TimerDisplay { DISPLAY_TIME_OF_DAY, DISPLAY_CONDITIONAL_TIMER, DISPLAY_NAVI_TIMER, + DISPLAY_FRED_QUEST, } TimerDisplay; typedef enum NaviTimerValues { diff --git a/soh/soh/Enhancements/bootcommands.c b/soh/soh/Enhancements/bootcommands.c index b9838cf66ad..f227b401e4d 100644 --- a/soh/soh/Enhancements/bootcommands.c +++ b/soh/soh/Enhancements/bootcommands.c @@ -11,6 +11,8 @@ void BootCommands_Init() { CVarClear(CVAR_GENERAL("OnFileSelectNameEntry")); // Clear when soh is killed on the file name entry page CVarClear(CVAR_GENERAL("BetterDebugWarpScreenMQMode")); CVarClear(CVAR_GENERAL("BetterDebugWarpScreenMQModeScene")); + + CVarClear(CVAR_REMOTE_ARCHIPELAGO("ConnectionStatus")); // Clear on boot to reset connection status #if defined(__SWITCH__) || defined(__WIIU__) CVarRegisterInteger(CVAR_IMGUI_CONTROLLER_NAV, 1); // always enable controller nav on switch/wii u #endif diff --git a/soh/soh/Enhancements/controls/SohInputEditorWindow.cpp b/soh/soh/Enhancements/controls/SohInputEditorWindow.cpp index 208833d6149..f26500f709b 100644 --- a/soh/soh/Enhancements/controls/SohInputEditorWindow.cpp +++ b/soh/soh/Enhancements/controls/SohInputEditorWindow.cpp @@ -1609,6 +1609,25 @@ void SohInputEditorWindow::DrawLinkTab() { .Color(THEME_COLOR) .Tooltip("Hold the assigned button to change the maximum walking or swimming speed")); if (CVarGetInteger(CVAR_SETTING("WalkModifier.Enabled"), 0)) { + CVarBtnSelector( + "Speed Modifier 1 Button Combo", CVAR_SETTING("WalkModifier.Mod1Btn"), + BtnSelectorOptions() + .DefaultValue(BTN_CUSTOM_MODIFIER1) + .Color(THEME_COLOR) + .Tooltip( + "Buttons that activate Speed Modifier 1.\n\n" + "If \"Toggle modifier instead of holding\" is off, hold this combo to apply the modifier.\n" + "If it is on, tap this combo to toggle the modifier on/off.")); + + CVarBtnSelector( + "Speed Modifier 2 Button Combo", CVAR_SETTING("WalkModifier.Mod2Btn"), + BtnSelectorOptions() + .DefaultValue(BTN_CUSTOM_MODIFIER2) + .Color(THEME_COLOR) + .Tooltip( + "Buttons that activate Speed Modifier 2.\n\n" + "If \"Toggle modifier instead of holding\" is off, hold this combo to apply the modifier.\n" + "If it is on, tap this combo to toggle the modifier on/off.")); UIWidgets::Spacer(5); Ship::GuiWindow::BeginGroupPanel("Speed Modifier", ImGui::GetContentRegionAvail()); CVarCheckbox("Toggle modifier instead of holding", CVAR_SETTING("WalkModifier.SpeedToggle"), diff --git a/soh/soh/Enhancements/cosmetics/CosmeticsEditor.cpp b/soh/soh/Enhancements/cosmetics/CosmeticsEditor.cpp index 94222b0b715..3c0930ed05c 100644 --- a/soh/soh/Enhancements/cosmetics/CosmeticsEditor.cpp +++ b/soh/soh/Enhancements/cosmetics/CosmeticsEditor.cpp @@ -46,6 +46,13 @@ extern "C" { #include "textures/nintendo_rogo_static/nintendo_rogo_static.h" #include "objects/object_gi_rabit_mask/object_gi_rabit_mask.h" #include "overlays/ovl_Magic_Wind/ovl_Magic_Wind.h" +#include "objects/object_wood02/object_wood02.h" +#include "scenes/overworld/spot00/spot00_room_0.h" +#include "scenes/overworld/spot04/spot04_room_0.h" +#include "scenes/overworld/spot04/spot04_room_1.h" +#include "scenes/overworld/spot20/spot20_room_0.h" +#include "scenes/overworld/spot03/spot03_room_0.h" +#include "scenes/overworld/spot15/spot15_room_0.h" extern PlayState* gPlayState; void ResourceMgr_PatchGfxByName(const char* path, const char* patchName, int index, Gfx instruction); @@ -204,7 +211,7 @@ Color_RGBA8 ColorRGBA8(uint8_t r, uint8_t g, uint8_t b, uint8_t a) { static std::map cosmeticOptions = { COSMETIC_OPTION("Link.KokiriTunic", "Kokiri Tunic", COSMETICS_GROUP_LINK, ColorRGBA8( 30, 105, 27, 255), false, true, false), COSMETIC_OPTION("Link.GoronTunic", "Goron Tunic", COSMETICS_GROUP_LINK, ColorRGBA8(100, 20, 0, 255), false, true, false), - COSMETIC_OPTION("Link.ZoraTunic", "Zora Tunic", COSMETICS_GROUP_LINK, ColorRGBA8( 0, 60, 100, 255), false, true, false), + COSMETIC_OPTION("Link.ZoraTunic", "Zora Tunic", COSMETICS_GROUP_LINK, ColorRGBA8( 0, 60, 100, 255), false, true, false), COSMETIC_OPTION("Link.Hair", "Hair", COSMETICS_GROUP_LINK, ColorRGBA8(255, 173, 27, 255), false, true, true), COSMETIC_OPTION("Link.Linen", "Linen", COSMETICS_GROUP_LINK, ColorRGBA8(255, 255, 255, 255), false, true, true), COSMETIC_OPTION("Link.Boots", "Boots", COSMETICS_GROUP_LINK, ColorRGBA8( 93, 44, 18, 255), false, true, true), @@ -255,7 +262,7 @@ static std::map cosmeticOptions = { COSMETIC_OPTION("Consumable.DDHeartBorder", "DD Heart Border", COSMETICS_GROUP_CONSUMABLE, ColorRGBA8(255, 255, 255, 255), false, true, true), COSMETIC_OPTION("Consumable.Magic", "Magic", COSMETICS_GROUP_CONSUMABLE, ColorRGBA8( 0, 200, 0, 255), false, true, false), COSMETIC_OPTION("Consumable.MagicActive", "Magic Active", COSMETICS_GROUP_CONSUMABLE, ColorRGBA8(250, 250, 0, 255), false, true, true), - COSMETIC_OPTION("Consumable_MagicInfinite", "Infinite Magic", COSMETICS_GROUP_CONSUMABLE, ColorRGBA8( 0, 0, 200, 255), false, true, true), + COSMETIC_OPTION("Consumable.MagicInfinite", "Infinite Magic", COSMETICS_GROUP_CONSUMABLE, ColorRGBA8( 0, 0, 200, 255), false, true, true), COSMETIC_OPTION("Consumable.MagicBorder", "Magic Border", COSMETICS_GROUP_CONSUMABLE, ColorRGBA8(255, 255, 255, 255), false, true, true), COSMETIC_OPTION("Consumable.MagicBorderActive", "Magic Border Active", COSMETICS_GROUP_CONSUMABLE, ColorRGBA8(255, 255, 255, 255), false, true, true), COSMETIC_OPTION("Consumable.GreenRupee", "Green Rupee", COSMETICS_GROUP_CONSUMABLE, ColorRGBA8( 50, 255, 50, 255), false, true, true), @@ -518,6 +525,11 @@ void ResetPositionAll() { int hue = 0; +#define CVAR_LL(v) \ + "gHoliday." \ + "LL" \ + "." v + // Runs every frame to update rainbow hue, a potential future optimization is to only run this a once or twice a second // and increase the speed of the rainbow hue rotation. void CosmeticsUpdateTick() { @@ -527,10 +539,43 @@ void CosmeticsUpdateTick() { if (cosmeticOption.supportsRainbow && CVarGetInteger(cosmeticOption.rainbowCvar, 0)) { double frequency = 2 * M_PI / (360 * rainbowSpeed); Color_RGBA8 newColor; - newColor.r = static_cast(sin(frequency * (hue + index) + 0) * 127) + 128; - newColor.g = static_cast(sin(frequency * (hue + index) + (2 * M_PI / 3)) * 127) + 128; - newColor.b = static_cast(sin(frequency * (hue + index) + (4 * M_PI / 3)) * 127) + 128; + newColor.a = 255; + + if (!CVarGetInteger(CVAR_LL("lEnableCustomRainbows"), 0)) { + newColor.r = static_cast(sin(frequency * (hue + index) + 0) * 127) + 128; + newColor.g = static_cast(sin(frequency * (hue + index) + (2 * M_PI / 3)) * 127) + 128; + newColor.b = static_cast(sin(frequency * (hue + index) + (4 * M_PI / 3)) * 127) + 128; + } else { + Color_RGBA8 customColorZero = CVarGetColor(CVAR_LL("lCustomRainbow1"), {}); + Color_RGBA8 customColorOne = CVarGetColor(CVAR_LL("lCustomRainbow2"), {}); + Color_RGBA8 customColorMinusZero = CVarGetColor(CVAR_LL("lCustomRainbow3"), {}); + Color_RGBA8 customColorMinusOne = CVarGetColor(CVAR_LL("lCustomRainbow4"), {}); + float sinangle = sin(frequency * (hue + index)); + bool quadrant1 = hue <= (360 * rainbowSpeed) / 4; + bool quadrant2 = hue >= (360 * rainbowSpeed) / 4 && hue <= (360 * rainbowSpeed) / 2; + bool quadrant3 = hue >= (360 * rainbowSpeed) / 2 && hue <= (360 * rainbowSpeed) * 3 / 4; + bool quadrant4 = hue >= (360 * rainbowSpeed) * 3 / 4; + + if (quadrant1) { // zero to one + newColor.r = sinangle * (customColorOne.r - customColorZero.r) + customColorZero.r; + newColor.g = sinangle * (customColorOne.g - customColorZero.g) + customColorZero.g; + newColor.b = sinangle * (customColorOne.b - customColorZero.b) + customColorZero.b; + } else if (quadrant2) { // one to zero + newColor.r = sinangle * (customColorOne.r - customColorMinusZero.r) + customColorMinusZero.r; + newColor.g = sinangle * (customColorOne.g - customColorMinusZero.g) + customColorMinusZero.g; + newColor.b = sinangle * (customColorOne.b - customColorMinusZero.b) + customColorMinusZero.b; + } else if (quadrant3) { // zero to minus one + newColor.r = -sinangle * (customColorMinusOne.r - customColorMinusZero.r) + customColorMinusZero.r; + newColor.g = -sinangle * (customColorMinusOne.g - customColorMinusZero.g) + customColorMinusZero.g; + newColor.b = -sinangle * (customColorMinusOne.b - customColorMinusZero.b) + customColorMinusZero.b; + } else if (quadrant4) { // minus one to zero + newColor.r = -sinangle * (customColorMinusOne.r - customColorZero.r) + customColorZero.r; + newColor.g = -sinangle * (customColorMinusOne.g - customColorZero.g) + customColorZero.g; + newColor.b = -sinangle * (customColorMinusOne.b - customColorZero.b) + customColorZero.b; + } + } + // For alpha supported options, retain the last set alpha instead of overwriting if (cosmeticOption.supportsAlpha) { newColor.a = static_cast(cosmeticOption.currentColor.w * 255.0f); @@ -1878,14 +1923,6 @@ void DrawSillyTab() { UIWidgets::Separator(true, true, 2.0f, 2.0f); - UIWidgets::CVarCheckbox("Let It Snow", CVAR_GENERAL("LetItSnow"), - UIWidgets::CheckboxOptions() - .Color(THEME_COLOR) - .Tooltip("Makes snow fall, changes chest texture colors to red and green, etc, for " - "December holidays.\nWill reset on restart outside of December 23-25.")); - - UIWidgets::Separator(true, true, 2.0f, 2.0f); - if (UIWidgets::CVarSliderFloat("Link Body Size", CVAR_COSMETIC("Link.BodySize.Value"), UIWidgets::FloatSliderOptions() .Format("%.3f") diff --git a/soh/soh/Enhancements/cosmetics/Emote.cpp b/soh/soh/Enhancements/cosmetics/Emote.cpp new file mode 100644 index 00000000000..7766ecd4be1 --- /dev/null +++ b/soh/soh/Enhancements/cosmetics/Emote.cpp @@ -0,0 +1,261 @@ +#include +#include +#include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h" +#include "soh/ShipInit.hpp" +#include "soh/SohGui/MenuTypes.h" +#include "soh/SohGui/SohMenu.h" +#include "soh/SohGui/SohGui.hpp" +#include "soh/SohGui/UIWidgets.hpp" + +extern "C" { +#include "assets/objects/gameplay_keep/gameplay_keep.h" +#include "macros.h" +#include "libultraship/libultra/controller.h" +extern PlayState* gPlayState; +void LinkAnimation_PlayLoopSetSpeed(PlayState* play, SkelAnime* skelAnime, LinkAnimationHeader* animation, + f32 playSpeed); +void LinkAnimation_PlayOnceSetSpeed(PlayState* play, SkelAnime* skelAnime, LinkAnimationHeader* animation, + f32 playSpeed); +} + +namespace SohGui { +extern std::shared_ptr mSohMenu; +} // namespace SohGui + +struct EmoteSlot { + std::string animationPath; + bool playOnce; + float speed; + std::string displayName; +}; + +static EmoteSlot emoteSlots[4] = { + { "", false, 1.0f, "C-Up" }, + { "", false, 1.0f, "C-Right" }, + { "", false, 1.0f, "C-Down" }, + { "", false, 1.0f, "C-Left" }, +}; + +static std::vector availableAnimations; + +static char animationSearchString[64] = ""; +static int16_t animationSearchDebounceFrames = -1; +static bool doAnimationSearch = false; +static bool wheelActive = false; +static EmoteSlot targetSlot; +static std::string activeAnimation = ""; + +// CVars for persistence +#define CVAR_EMOTE(slot, field) ("gEmoteWheel.Slot" + std::to_string(slot) + "." + field).c_str() + +void LoadEmoteConfiguration() { + for (int i = 0; i < 4; i++) { + std::string animPath = CVarGetString(CVAR_EMOTE(i, "Animation"), emoteSlots[i].animationPath.c_str()); + if (!animPath.empty()) { + emoteSlots[i].animationPath = animPath; + } + emoteSlots[i].playOnce = CVarGetInteger(CVAR_EMOTE(i, "PlayOnce"), emoteSlots[i].playOnce ? 1 : 0); + emoteSlots[i].speed = CVarGetFloat(CVAR_EMOTE(i, "Speed"), emoteSlots[i].speed); + } +} + +void SaveEmoteConfiguration() { + for (int i = 0; i < 4; i++) { + CVarSetString(CVAR_EMOTE(i, "Animation"), emoteSlots[i].animationPath.c_str()); + CVarSetInteger(CVAR_EMOTE(i, "PlayOnce"), emoteSlots[i].playOnce ? 1 : 0); + CVarSetFloat(CVAR_EMOTE(i, "Speed"), emoteSlots[i].speed); + } + CVarSave(); +} + +void LoadAvailableAnimations() { + availableAnimations.clear(); + + // Get all available animations + auto result = Ship::Context::GetInstance()->GetResourceManager()->GetArchiveManager()->ListFiles( + "*gPlayerAnim_link*" + std::string(animationSearchString) + "*"); + + availableAnimations.clear(); + for (const auto& path : *result.get()) { + availableAnimations.push_back("__OTR__" + path); + } +} + +std::string GetAnimationDisplayName(const std::string& path) { + // Extract a friendly name from the path + size_t lastSlash = path.find_last_of('/'); + std::string name = (lastSlash != std::string::npos) ? path.substr(lastSlash + 1) : path; + + // Remove common prefixes + if (name.find("gPlayerAnim_link") == 0) { + name = name.substr(17); + } + + return name; +} + +int GetSelectedSlotFromCButtons(uint16_t buttons) { + if (buttons & BTN_CUP) { + return 0; + } else if (buttons & BTN_CRIGHT) { + return 1; + } else if (buttons & BTN_CDOWN) { + return 2; + } else if (buttons & BTN_CLEFT) { + return 3; + } + return -1; +} + +void RegisterEmote() { + LoadAvailableAnimations(); + LoadEmoteConfiguration(); + + COND_HOOK(OnPlayerUpdate, CVarGetInteger("gEmoteWheel.Enabled", 0), []() { + Player* player = GET_PLAYER(gPlayState); + + if (player->stateFlags1 & PLAYER_STATE1_GETTING_ITEM || player->stateFlags1 & PLAYER_STATE1_CLIMBING_LADDER || + player->stateFlags1 & PLAYER_STATE1_IN_WATER || player->stateFlags1 & PLAYER_STATE1_IN_ITEM_CS) { + return; + } + + if (targetSlot.animationPath != "") { + activeAnimation = targetSlot.animationPath; + if (targetSlot.playOnce) { + LinkAnimation_PlayOnceSetSpeed(gPlayState, &player->skelAnime, + (LinkAnimationHeader*)activeAnimation.c_str(), targetSlot.speed); + } else { + LinkAnimation_PlayLoopSetSpeed(gPlayState, &player->skelAnime, + (LinkAnimationHeader*)activeAnimation.c_str(), targetSlot.speed); + } + targetSlot.animationPath = ""; + } + }); + + COND_HOOK(OnGameStateMainStart, CVarGetInteger("gEmoteWheel.Enabled", 0), []() { + if (!gPlayState) + return; + + Player* player = GET_PLAYER(gPlayState); + if (!player) + return; + + Input* input = &gPlayState->state.input[0]; + + if (input->press.button & BTN_L) { + input->press.button &= ~BTN_L; // Capture + wheelActive = true; + } + + if (!(input->cur.button & BTN_L) && wheelActive) { + wheelActive = false; + } + + if (wheelActive) { + auto slot = GetSelectedSlotFromCButtons(input->cur.button); + if (slot >= 0 && slot < 4) { + targetSlot = emoteSlots[slot]; + } + input->press.button &= ~BTN_CUP & ~BTN_CRIGHT & ~BTN_CDOWN & ~BTN_CLEFT; // Capture + } + }); +} + +void DrawEmoteConfiguration(WidgetInfo& info) { + ImGui::Text("Hold L button and press a C-button to select an emote"); + UIWidgets::CVarCheckbox("Enabled", "gEmoteWheel.Enabled", UIWidgets::CheckboxOptions().Color(THEME_COLOR)); + + if (!CVarGetInteger("gEmoteWheel.Enabled", 0)) { + return; + } + + UIWidgets::PushStyleInput(THEME_COLOR); + + if (ImGui::InputText("Search Animations", animationSearchString, ARRAY_COUNT(animationSearchString))) { + doAnimationSearch = true; + animationSearchDebounceFrames = 30; + } + UIWidgets::PopStyleInput(); + + if (doAnimationSearch) { + if (animationSearchDebounceFrames == 0) { + doAnimationSearch = false; + LoadAvailableAnimations(); + } + + animationSearchDebounceFrames--; + } + + ImGui::Separator(); + ImGui::Spacing(); + + const char* slotNames[] = { "C-Up", "C-Right", "C-Down", "C-Left" }; + + for (int i = 0; i < 4; i++) { + ImGui::PushID(i); + + ImGui::Text("%s Slot", slotNames[i]); + + std::string currentAnimName = GetAnimationDisplayName(emoteSlots[i].animationPath); + UIWidgets::PushStyleCombobox(THEME_COLOR); + if (ImGui::BeginCombo("Animation", currentAnimName.c_str())) { + for (const auto& animPath : availableAnimations) { + bool isSelected = (emoteSlots[i].animationPath == animPath); + std::string displayName = GetAnimationDisplayName(animPath); + if (ImGui::Selectable(displayName.c_str(), isSelected)) { + emoteSlots[i].animationPath = animPath; + SaveEmoteConfiguration(); + } + if (isSelected) { + ImGui::SetItemDefaultFocus(); + } + } + ImGui::EndCombo(); + } + UIWidgets::PopStyleCombobox(); + + // Play once checkbox + bool playOnce = emoteSlots[i].playOnce; + UIWidgets::PushStyleCheckbox(THEME_COLOR); + if (ImGui::Checkbox("Play Once", &playOnce)) { + emoteSlots[i].playOnce = playOnce; + SaveEmoteConfiguration(); + } + UIWidgets::PopStyleCheckbox(); + + // Speed slider + float speed = emoteSlots[i].speed; + ImGui::SetNextItemWidth(200.0f); + UIWidgets::PushStyleSlider(THEME_COLOR); + if (ImGui::SliderFloat("Speed", &speed, 0.1f, 3.0f, "%.2f")) { + emoteSlots[i].speed = speed; + SaveEmoteConfiguration(); + } + UIWidgets::PopStyleSlider(); + + ImGui::Spacing(); + ImGui::Separator(); + ImGui::Spacing(); + + ImGui::PopID(); + } + + if (UIWidgets::Button("Reset to Defaults", { .size = UIWidgets::Sizes::Inline })) { + emoteSlots[0] = { "", false, 1.0f, "Up" }; + emoteSlots[1] = { "", false, 1.0f, "Right" }; + emoteSlots[2] = { "", false, 1.0f, "Down" }; + emoteSlots[3] = { "", false, 1.0f, "Left" }; + SaveEmoteConfiguration(); + } +} + +void RegisterEmoteWidgets() { + SohGui::mSohMenu->AddSidebarEntry("Settings", "Emotes", 1); + WidgetPath path = { "Settings", "Emotes", SECTION_COLUMN_1 }; + SohGui::mSohMenu->AddWidget(path, "Emote Wheel Config", WIDGET_CUSTOM) + .CustomFunction(DrawEmoteConfiguration) + .HideInSearch(true); +} + +static RegisterMenuInitFunc menuInitFunc(RegisterEmoteWidgets); +static RegisterShipInitFunc initFunc(RegisterEmote, { "gEmoteWheel.Enabled" }); diff --git a/soh/soh/Enhancements/custom-item/CustomItem.cpp b/soh/soh/Enhancements/custom-item/CustomItem.cpp new file mode 100644 index 00000000000..d3bde51e3cb --- /dev/null +++ b/soh/soh/Enhancements/custom-item/CustomItem.cpp @@ -0,0 +1,262 @@ +#include "CustomItem.h" +#include "soh/Enhancements/game-interactor/GameInteractor.h" + +extern "C" { +#include "z64actor.h" +#include "functions.h" +#include "variables.h" +#include "macros.h" +extern PlayState* gPlayState; +} + +// #region These were copied from z_en_item00.c +static ColliderCylinderInit sCylinderInit = { + { + COLTYPE_NONE, + AT_NONE, + AC_ON | AT_TYPE_PLAYER, + OC1_NONE, + OC2_NONE, + COLSHAPE_CYLINDER, + }, + { + ELEMTYPE_UNK0, + { 0x00000000, 0x00, 0x00 }, + { 0x00000010, 0x00, 0x00 }, + TOUCH_NONE | TOUCH_SFX_NORMAL, + BUMP_ON, + OCELEM_NONE, + }, + { 10, 30, 0, { 0, 0, 0 } }, +}; + +static InitChainEntry sInitChain[] = { + ICHAIN_F32(targetArrowOffset, 2000, ICHAIN_STOP), +}; +// #endregion + +EnItem00* CustomItem::Spawn(f32 posX, f32 posY, f32 posZ, s16 rot, s16 flags, s16 params, ActorFunc actionFunc, + ActorFunc drawFunc) { + if (!gPlayState) { + return nullptr; + } + + Actor* actor = Actor_Spawn(&gPlayState->actorCtx, gPlayState, ACTOR_EN_ITEM00, posX, posY, posZ, flags, rot, params, + ITEM00_NONE, false); + EnItem00* enItem00 = (EnItem00*)actor; + + if (actionFunc != NULL) { + enItem00->actionFunc = (EnItem00ActionFunc)actionFunc; + } + + if (drawFunc != NULL) { + actor->draw = drawFunc; + } + + return enItem00; +} + +void CustomItem_Init(Actor* actor, PlayState* play) { + EnItem00* enItem00 = (EnItem00*)actor; + + if (CUSTOM_ITEM_FLAGS & CustomItem::STOP_BOBBING) { + actor->shape.yOffset = 1250.0f; + } else { + actor->shape.yOffset = (Math_SinS(actor->shape.rot.y) * 150.0f) + 1250.0f; + } + + if (CUSTOM_ITEM_FLAGS & CustomItem::HIDE_TILL_OVERHEAD) { + Actor_SetScale(actor, 0.0f); + } else { + Actor_SetScale(actor, 0.015f); + } + + if (CUSTOM_ITEM_FLAGS & CustomItem::KEEP_ON_PLAYER) { + Math_Vec3f_Copy(&actor->world.pos, &GET_PLAYER(play)->actor.world.pos); + } + + if (CUSTOM_ITEM_FLAGS & CustomItem::TOSS_ON_SPAWN) { + actor->velocity.y = 8.0f; + actor->speedXZ = 2.0f; + actor->world.rot.y = Rand_ZeroOne() * 40000.0f; + } + if (CUSTOM_ITEM_FLAGS & CustomItem::TOSS_ON_SPAWN || CUSTOM_ITEM_FLAGS & CustomItem::ENABLE_GRAVITY) { + actor->gravity = -1.4f; + } + + Actor_ProcessInitChain(actor, sInitChain); + Collider_InitCylinder(play, &enItem00->collider); + Collider_SetCylinder(play, &enItem00->collider, actor, &sCylinderInit); + + enItem00->unk_15A = -1; +} + +// By default this will just assume the GID was passed in as the rot z, if you want different functionality you should +// override the draw +void CustomItem_Draw(Actor* actor, PlayState* play) { + Matrix_Scale(30.0f, 30.0f, 30.0f, MTXMODE_APPLY); + GetItem_Draw(play, CUSTOM_ITEM_PARAM); +} + +// Once the item is touched we need to clear movement vars so the item doesn't sink in the players hands/above head +void CustomItem_ItemTouched(Actor* actor, PlayState* play) { + actor->speedXZ = 0.0f; + actor->velocity.y = 0.0f; + actor->gravity = 0.0f; + actor->shape.yOffset = 1250.0f; +} + +void CustomItem_Update(Actor* actor, PlayState* play) { + EnItem00* enItem00 = (EnItem00*)actor; + Player* player = GET_PLAYER(play); + + if (!(CUSTOM_ITEM_FLAGS & CustomItem::STOP_SPINNING)) { + actor->shape.rot.y += 960; + } + + if (!(CUSTOM_ITEM_FLAGS & CustomItem::STOP_BOBBING)) { + actor->shape.yOffset = (Math_SinS(actor->shape.rot.y) * 150.0f) + 1250.0f; + } + + if (CUSTOM_ITEM_FLAGS & CustomItem::HIDE_TILL_OVERHEAD) { + Actor_SetScale(actor, 0.0f); + } + + if (CUSTOM_ITEM_FLAGS & CustomItem::KEEP_ON_PLAYER) { + Math_Vec3f_Copy(&actor->world.pos, &GET_PLAYER(play)->actor.world.pos); + } + + // Player range check accounting for goron rolling behavior. Matches EnItem00 range check. + bool playerInRangeOfPickup = (actor->xzDistToPlayer <= 30.0f) && (fabsf(actor->yDistToPlayer) <= fabsf(50.0f)); + + if (CUSTOM_ITEM_FLAGS & CustomItem::KILL_ON_TOUCH) { + // Pretty self explanatory, if the player is within range, kill the actor and call the action function + if (playerInRangeOfPickup) { + if (enItem00->actionFunc != NULL) { + enItem00->actionFunc(enItem00, play); + CUSTOM_ITEM_FLAGS |= CustomItem::CALLED_ACTION; + } + Actor_Kill(actor); + } + } else if (CUSTOM_ITEM_FLAGS & CustomItem::GIVE_OVERHEAD) { + // If the item hasn't been picked up (unk_15A == -1) and the player is within range + if (enItem00->unk_15A == -1 && playerInRangeOfPickup) { + // Fire the action function + if (enItem00->actionFunc != NULL) { + enItem00->actionFunc(enItem00, play); + CUSTOM_ITEM_FLAGS |= CustomItem::CALLED_ACTION; + } + Sfx_PlaySfxCentered(NA_SE_SY_GET_ITEM); + // Set the unk_15A to 15, this indicates the item has been picked up and will start the overhead animation + enItem00->unk_15A = 15; + CUSTOM_ITEM_FLAGS |= CustomItem::STOP_BOBBING; + CUSTOM_ITEM_FLAGS |= CustomItem::KEEP_ON_PLAYER; + CustomItem_ItemTouched(actor, play); + // Move to player right away on this frame + Math_Vec3f_Copy(&actor->world.pos, &GET_PLAYER(play)->actor.world.pos); + } + + // If the item has been picked up + if (enItem00->unk_15A > 0) { + // Reduce the size a bit, but also makes it visible for HIDE_TILL_OVERHEAD + Actor_SetScale(actor, 0.010f); + + // Decrement the unk_15A, which will be used to bob the item up and down + enItem00->unk_15A--; + + // Account for the different heights of the player forms + f32 height = LINK_IS_ADULT ? 60.0f : 45.0f; + + // Bob the item up and down + actor->world.pos.y += (height + (Math_SinS(enItem00->unk_15A * 15000) * (enItem00->unk_15A * 0.3f))); + } + + // Finally, once the bobbing animation is done, kill the actor + if (enItem00->unk_15A == 0) { + Actor_Kill(actor); + } + } else if (CUSTOM_ITEM_FLAGS & CustomItem::GIVE_ITEM_CUTSCENE) { + // If the item hasn't been picked up and the player is within range + + if (!Actor_HasParent(actor, play) && enItem00->unk_15A == -1) { + Actor_OfferGetItem(actor, play, GI_SHIP, 50.0f, 80.0f); + } else { + if (enItem00->unk_15A == -1) { + // actor->shape.yOffset = 1250.0f; + CUSTOM_ITEM_FLAGS |= CustomItem::STOP_BOBBING; + // Math_Vec3f_Copy(&actor->world.pos, &GET_PLAYER(play)->actor.world.pos); + CUSTOM_ITEM_FLAGS |= CustomItem::KEEP_ON_PLAYER; + // Actor_SetScale(actor, 0.0f); + CUSTOM_ITEM_FLAGS |= CustomItem::HIDE_TILL_OVERHEAD; + CustomItem_ItemTouched(actor, play); + } + + // Begin incrementing the unk_15A, indicating the item has been picked up + enItem00->unk_15A++; + + // For the first 20 frames, wait while the player's animation plays + if (enItem00->unk_15A >= 20) { + // After the first 20 frames, show the item and call the action function + if (enItem00->unk_15A == 20 && enItem00->actionFunc != NULL) { + enItem00->actionFunc(enItem00, play); + CUSTOM_ITEM_FLAGS |= CustomItem::CALLED_ACTION; + } + // Override the bobbing animation to be a fixed height + actor->shape.yOffset = 900.0f; + Actor_SetScale(actor, 0.007f); + + // Account for the different heights of the player forms + f32 height = LINK_IS_ADULT ? 60.0f : 45.0f; + + actor->world.pos.y += height; + } + + // Once the player is no longer in the "Give Item" state, kill the actor + if (!(player->stateFlags1 & PLAYER_STATE1_GETTING_ITEM)) { + Actor_Kill(actor); + } + } + } + + if (actor->gravity != 0.0f) { + Actor_MoveXZGravity(actor); + Actor_UpdateBgCheckInfo(play, actor, 20.0f, 15.0f, 15.0f, 0x1D); + } + + if (actor->bgCheckFlags & 0x0003) { + actor->speedXZ = 0.0f; + } + + if (CUSTOM_ITEM_FLAGS & CustomItem::ABLE_TO_BOOMERANG) { + Collider_UpdateCylinder(actor, &enItem00->collider); + CollisionCheck_SetAC(play, &play->colChkCtx, &enItem00->collider.base); + } +} + +void CustomItem_Destroy(Actor* actor, PlayState* play) { + EnItem00* enItem00 = (EnItem00*)actor; + + Collider_DestroyCylinder(play, &enItem00->collider); +} + +void CustomItem::RegisterHooks() { + GameInteractor::Instance->RegisterGameHookForID( + ACTOR_EN_ITEM00, [](void* actorRef, bool* should) { + Actor* actor = (Actor*)actorRef; + if (actor->params != ITEM00_NONE) { + return; + } + + actor->init = CustomItem_Init; + actor->update = CustomItem_Update; + actor->draw = CustomItem_Draw; + actor->destroy = CustomItem_Destroy; + + // Set the rotX/rotZ back to 0, the original values can be accessed from actor->home + actor->world.rot.x = 0; + actor->world.rot.z = 0; + actor->shape.rot.x = 0; + actor->shape.rot.y = 0; + actor->shape.rot.z = 0; + }); +} diff --git a/soh/soh/Enhancements/custom-item/CustomItem.h b/soh/soh/Enhancements/custom-item/CustomItem.h new file mode 100644 index 00000000000..6292a124ba1 --- /dev/null +++ b/soh/soh/Enhancements/custom-item/CustomItem.h @@ -0,0 +1,31 @@ +#ifndef CUSTOM_ITEM_H +#define CUSTOM_ITEM_H + +extern "C" { +#include "z64actor.h" +} + +#define CUSTOM_ITEM_FLAGS (actor->home.rot.x) +#define CUSTOM_ITEM_PARAM (actor->home.rot.z) + +namespace CustomItem { + +enum CustomItemFlags : int16_t { + KILL_ON_TOUCH = 1 << 0, // 0000 0000 0000 0001 + GIVE_OVERHEAD = 1 << 1, // 0000 0000 0000 0010 + GIVE_ITEM_CUTSCENE = 1 << 2, // 0000 0000 0000 0100 + HIDE_TILL_OVERHEAD = 1 << 3, // 0000 0000 0000 1000 + KEEP_ON_PLAYER = 1 << 4, // 0000 0000 0001 0000 + STOP_BOBBING = 1 << 5, // 0000 0000 0010 0000 + STOP_SPINNING = 1 << 6, // 0000 0000 0100 0000 + CALLED_ACTION = 1 << 7, // 0000 0000 1000 0000 + TOSS_ON_SPAWN = 1 << 8, // 0000 0001 0000 0000 + ABLE_TO_BOOMERANG = 1 << 9, // 0000 0010 0000 0000 + ENABLE_GRAVITY = 1 << 10, // 0000 0100 0000 0000 +}; +void RegisterHooks(); +EnItem00* Spawn(f32 posX, f32 posY, f32 posZ, s16 rot, s16 flags, s16 params, ActorFunc actionFunc = NULL, + ActorFunc drawFunc = NULL); +}; // namespace CustomItem + +#endif // CUSTOM_ITEM_H diff --git a/soh/soh/Enhancements/custom-message/CustomMessageManager.cpp b/soh/soh/Enhancements/custom-message/CustomMessageManager.cpp index 0ebba19ec26..9009024cd67 100644 --- a/soh/soh/Enhancements/custom-message/CustomMessageManager.cpp +++ b/soh/soh/Enhancements/custom-message/CustomMessageManager.cpp @@ -1,5 +1,7 @@ #include "CustomMessageManager.h" #include "CustomMessageInterfaceAddon.h" +#include "CustomMessageTypes.h" +#include "soh/Enhancements/game-interactor/GameInteractor.h" #include #include #include @@ -7,6 +9,14 @@ #include #include +#include "soh/util.h" + +extern "C" { +#include "functions.h" + +extern PlayState* gPlayState; +} + using namespace std::literals::string_literals; static const std::unordered_map textBoxSpecialCharacters = { @@ -212,6 +222,27 @@ const TextBoxPosition& CustomMessage::GetTextBoxPosition() const { return position; } +void CustomMessage::LoadIntoFont() { + MessageContext* msgCtx = &gPlayState->msgCtx; + Font* font = &msgCtx->font; + char* buffer = font->msgBuf; + const int maxBufferSize = sizeof(font->msgBuf); + + font->charTexBuf[0] = (type << 4) | position; + + std::string content = GetEnglish(MF_RAW); + switch (gSaveContext.language) { + case LANGUAGE_FRA: + content = GetFrench(MF_RAW); + break; + case LANGUAGE_GER: + content = GetGerman(MF_RAW); + break; + } + + msgCtx->msgLength = font->msgLength = SohUtils::CopyStringToCharBuffer(buffer, content, maxBufferSize); +} + CustomMessage CustomMessage::operator+(const CustomMessage& right) const { std::vector newColors = colors; std::vector rColors = right.GetColors(); @@ -821,3 +852,21 @@ bool CustomMessageManager::AddCustomMessageTable(std::string tableID) { CustomMessageTable newMessageTable; return messageTables.emplace(tableID, newMessageTable).second; } + +void CustomMessageManager::SetActiveCustomMessage(CustomMessage message) { + activeCustomMessage = message; +} + +void CustomMessageManager::StartTextbox(CustomMessage message) { + activeCustomMessage = message; + + Message_StartTextbox(gPlayState, TEXT_CUSTOM_MESSAGE, &GET_PLAYER(gPlayState)->actor); +} + +void CustomMessageManager::RegisterHooks() { + GameInteractor::Instance->RegisterGameHookForID( + TEXT_CUSTOM_MESSAGE, [&](u16* textId, bool* loadFromMessageTable) { + *loadFromMessageTable = false; + activeCustomMessage.LoadIntoFont(); + }); +} diff --git a/soh/soh/Enhancements/custom-message/CustomMessageManager.h b/soh/soh/Enhancements/custom-message/CustomMessageManager.h index 43b89847645..c72fdc163d1 100644 --- a/soh/soh/Enhancements/custom-message/CustomMessageManager.h +++ b/soh/soh/Enhancements/custom-message/CustomMessageManager.h @@ -77,6 +77,9 @@ class CustomMessage { void SetTextBoxType(TextBoxType boxType); const TextBoxPosition& GetTextBoxPosition() const; + // To only be used with OnOpenText hook + void LoadIntoFont(); + CustomMessage operator+(const CustomMessage& right) const; CustomMessage operator+(const std::string& right) const; void operator+=(const std::string& right); @@ -244,11 +247,15 @@ class CustomMessageManager { bool InsertCustomMessage(std::string tableID, uint16_t textID, CustomMessage message); + CustomMessage activeCustomMessage; + public: static CustomMessageManager* Instance; CustomMessageManager() = default; + void RegisterHooks(); + /** * @brief Formats the provided Custom Message Entry and inserts it into the table with the provided tableID, * with the provided giid (getItemID) as its key. This function also inserts the icon corresponding to @@ -307,6 +314,22 @@ class CustomMessageManager { * already exists.) */ bool AddCustomMessageTable(std::string tableID); + + /** + * @brief Sets the active custom message, which will be used the next time + * TEXT_CUSTOM_MESSAGE is used for a text box. + * + * @param message the message to set as active + */ + void SetActiveCustomMessage(CustomMessage message); + + /** + * @brief Displays a custom message in a textbox. This is the same as calling + * SetActiveCustomMessage and then beginning a textbox with TEXT_CUSTOM_MESSAGE. + * + * @param message the message to set as active + */ + void StartTextbox(CustomMessage message); }; class MessageNotFoundException : public std::exception { diff --git a/soh/soh/Enhancements/custom-message/CustomMessageTypes.h b/soh/soh/Enhancements/custom-message/CustomMessageTypes.h index 86d0d485666..16bb1961fad 100644 --- a/soh/soh/Enhancements/custom-message/CustomMessageTypes.h +++ b/soh/soh/Enhancements/custom-message/CustomMessageTypes.h @@ -31,6 +31,7 @@ typedef enum { TEXT_PURPLE_RUPEE = 0x00F1, TEXT_HUGE_RUPEE = 0x00F2, TEXT_RANDOMIZER_CUSTOM_ITEM = 0x00F8, + TEXT_CUSTOM_MESSAGE = 0x0109, TEXT_NAVI_DEKU_TREE_SUMMONS = 0x0140, TEXT_NAVI_CMON_BE_BRAVE = 0x0141, TEXT_NAVI_VISIT_THE_PRINCESS = 0x0142, diff --git a/soh/soh/Enhancements/cvars/CvarCatalog.cpp b/soh/soh/Enhancements/cvars/CvarCatalog.cpp new file mode 100644 index 00000000000..f1a245ac2e9 --- /dev/null +++ b/soh/soh/Enhancements/cvars/CvarCatalog.cpp @@ -0,0 +1,139 @@ +#include "CvarCatalog.h" + +#include + +#include "soh/Enhancements/game-interactor/GameInteractor.h" +#include "soh/ShipInit.hpp" +#include "functions.h" +#include "soh/OTRGlobals.h" + +#include +#include +#include +#include + +extern "C" { +#include "z64.h" +#include "variables.h" +#include "macros.h" +} + +namespace { +std::vector sEntries; +std::mutex sMutex; +} // namespace + +namespace CVarCatalog { + +void Register(const char* cvarName, const char* label) { + if (cvarName == nullptr || cvarName[0] == '\0') { + return; + } + + std::scoped_lock lock(sMutex); + + // De-dupe by name; upgrade label if we learn a better one later + for (auto& e : sEntries) { + if (e.name == cvarName) { + if ((e.label.empty() || e.label == e.name) && label != nullptr && label[0] != '\0') { + e.label = label; + } + return; + } + } + + CVarCatalogEntry e; + e.name = cvarName; + e.label = (label != nullptr && label[0] != '\0') ? label : cvarName; + sEntries.push_back(std::move(e)); +} + +const std::vector& GetAll() { + return sEntries; +} + +// ============================================================================ +// CVar Binds hotkeys (checkbox CVars only) +// +// Stored in CVars: +// gSettings.CVarBinds.Count +// gSettings.CVarBinds.Entries.Entry.CVar (string) +// gSettings.CVarBinds.Entries.Entry.Mask (int32 packed; low 16 bits used) +// +// IMPORTANT: +// Do NOT use numeric-only path segments like ".0." (it can be treated as an array +// index by config unflattening and break string-key lookups after reload). +// ============================================================================ + +static constexpr int32_t CVAR_CVAR_BINDS_COUNT_DEFAULT = 0; +#define CVAR_CVAR_BINDS_COUNT_NAME "gSettings.CVarBinds.Count" + +static std::string GetBindPrefix(int index) { + return "gSettings.CVarBinds.Entries.Entry" + std::to_string(index); +} + +static std::string GetBindCVarName(int index) { + return GetBindPrefix(index) + ".CVar"; +} + +static std::string GetBindMaskName(int index) { + return GetBindPrefix(index) + ".Mask"; +} + +static void TryToggleBoundCVar(const char* targetCvarName) { + if (targetCvarName == nullptr || targetCvarName[0] == '\0') { + return; + } + + const int32_t cur = CVarGetInteger(targetCvarName, 0); + CVarSetInteger(targetCvarName, cur ? 0 : 1); +} + +static void OnGameStateMainStartCVarBindsHotkeys() { + const int32_t count = CVarGetInteger(CVAR_CVAR_BINDS_COUNT_NAME, CVAR_CVAR_BINDS_COUNT_DEFAULT); + if (count <= 0) { + return; + } + + // If two binds share the same combo, only fire once per frame for that mask. + static std::unordered_set sFiredThisFrame; + sFiredThisFrame.clear(); + + for (int i = 0; i < count; i++) { + const std::string maskName = GetBindMaskName(i); + const int32_t packed = CVarGetInteger(maskName.c_str(), 0); + const uint16_t mask = static_cast(packed & 0xFFFF); + + if (mask == 0) { + continue; + } + + if (sFiredThisFrame.contains(mask)) { + continue; + } + + if (CHECK_BTN_ANY(gGameState->input[0].press.button, mask) && + CHECK_BTN_ALL(gGameState->input[0].cur.button, mask)) { + + const std::string bindCvarName = GetBindCVarName(i); + const char* target = CVarGetString(bindCvarName.c_str(), ""); + + TryToggleBoundCVar(target); + + // Re-run any RegisterShipInitFunc handlers that listed this CVar as an update path. + if (target != nullptr && target[0] != '\0') { + ShipInit::Init(target); + } + + sFiredThisFrame.insert(mask); + } + } +} + +static void RegisterCVarBindsHotkeys() { + COND_HOOK(OnGameStateMainStart, true, OnGameStateMainStartCVarBindsHotkeys); +} + +static RegisterShipInitFunc initFuncCVarBindsHotkeys(RegisterCVarBindsHotkeys, { CVAR_CVAR_BINDS_COUNT_NAME }); + +} // namespace CVarCatalog diff --git a/soh/soh/Enhancements/cvars/CvarCatalog.h b/soh/soh/Enhancements/cvars/CvarCatalog.h new file mode 100644 index 00000000000..700177d7a5e --- /dev/null +++ b/soh/soh/Enhancements/cvars/CvarCatalog.h @@ -0,0 +1,14 @@ +#pragma once + +#include +#include + +struct CVarCatalogEntry { + std::string name; // CVar key (ex: "gSettings.Menu.Popout") + std::string label; // Widget label (ex: "Popout Menu") +}; + +namespace CVarCatalog { +void Register(const char* cvarName, const char* label); +const std::vector& GetAll(); +} // namespace CVarCatalog diff --git a/soh/soh/Enhancements/debugger/debugSaveEditor.cpp b/soh/soh/Enhancements/debugger/debugSaveEditor.cpp index 463f900e0d4..214cf521d05 100644 --- a/soh/soh/Enhancements/debugger/debugSaveEditor.cpp +++ b/soh/soh/Enhancements/debugger/debugSaveEditor.cpp @@ -65,6 +65,7 @@ IntSliderOptions intSliderOptionsBase; ButtonOptions buttonOptionsBase; CheckboxOptions checkboxOptionsBase; ComboboxOptions comboboxOptionsBase; +static std::map flagTableFilters; // Modification of gAmmoItems that replaces ITEM_NONE with the item in inventory slot it represents u8 gAllAmmoItems[] = { @@ -582,6 +583,75 @@ void DrawFlagTableArray16(const FlagTable& flagTable, uint16_t row, uint16_t& fl ImGui::PopID(); } +static uint16_t& GetFlagTableEntry(const FlagTable& flagTable, size_t row) { + switch (flagTable.flagTableType) { + case EVENT_CHECK_INF: + return gSaveContext.eventChkInf[row]; + case ITEM_GET_INF: + return gSaveContext.itemGetInf[row]; + case INF_TABLE: + return gSaveContext.infTable[row]; + case EVENT_INF: + return gSaveContext.eventInf[row]; + case RANDOMIZER_INF: + return gSaveContext.ship.randomizerInf[row]; + default: // Shouldn't be hit + assert(false); + return gSaveContext.eventChkInf[row]; + } +} + +static void DrawFlagTableSearchResults(const FlagTable& flagTable, ImGuiTextFilter& filter) { + bool hasMatches = false; + + for (size_t row = 0; row < flagTable.size + 1; row++) { + uint16_t& flags = GetFlagTableEntry(flagTable, row); + + for (int32_t flagIndex = 15; flagIndex >= 0; flagIndex--) { + uint16_t index = row * 16 + flagIndex; + auto descIt = flagTable.flagDescriptions.find(index); + const char* desc = descIt != flagTable.flagDescriptions.end() ? descIt->second : ""; + std::string searchable = fmt::format("0x{:02X} {}", index, desc); + if (!filter.PassFilter(searchable.c_str())) { + continue; + } + + hasMatches = true; + + ImGui::PushID(index); + bool hasDescription = descIt != flagTable.flagDescriptions.end(); + uint32_t bitMask = 1 << flagIndex; + ImVec4 themeColor = ColorValues.at(THEME_COLOR); + ImVec4 colorDark = { themeColor.x * 0.4f, themeColor.y * 0.4f, themeColor.z * 0.4f, themeColor.z }; + PushStyleCheckbox(hasDescription ? themeColor : colorDark); + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(4.0f, 3.0f)); + bool flag = (flags & bitMask) != 0; + if (ImGui::Checkbox("##check", &flag)) { + if (flag) { + flags |= bitMask; + } else { + flags &= ~bitMask; + } + } + ImGui::PopStyleVar(); + PopStyleCheckbox(); + + ImGui::SameLine(); + if (hasDescription) { + ImGui::TextWrapped("0x%02X: %s", index, desc); + } else { + ImGui::Text("0x%02X", index); + } + + ImGui::PopID(); + } + } + + if (!hasMatches) { + ImGui::Text("No flags match the current search."); + } +} + void DrawFlagsTab() { if (ImGui::TreeNode("Player State")) { if (gPlayState != nullptr) { @@ -922,44 +992,54 @@ void DrawFlagsTab() { }, "Gold Skulltulas"); - for (int i = 0; i < flagTables.size(); i++) { + for (size_t i = 0; i < flagTables.size(); i++) { const FlagTable& flagTable = flagTables[i]; if (flagTable.flagTableType == RANDOMIZER_INF && !IS_RANDO && !IS_BOSS_RUSH) { continue; } if (ImGui::TreeNode(flagTable.name)) { - for (int j = 0; j < flagTable.size + 1; j++) { - DrawGroupWithBorder( - [&]() { - if (j == 0) { - for (int k = 0xF; k >= 0; k--) { - ImGui::SameLine(37.5 + ((0xF - k) * 33.8)); - ImGui::Text("%X", k); + ImGui::PushID(flagTable.name); + ImGuiTextFilter& flagFilter = flagTableFilters[flagTable.name]; + ImGui::SetNextItemWidth(ImGui::GetFontSize() * 16); + flagFilter.Draw(); + ImGui::Spacing(); + + if (!flagFilter.IsActive()) { + for (size_t j = 0; j < flagTable.size + 1; j++) { + DrawGroupWithBorder( + [&]() { + if (j == 0) { + for (int k = 0xF; k >= 0; k--) { + ImGui::SameLine(37.5 + ((0xF - k) * 33.8)); + ImGui::Text("%X", k); + } } - } - ImGui::Text("%s", fmt::format("{:<2X}", j).c_str()); - - switch (flagTable.flagTableType) { - case EVENT_CHECK_INF: - DrawFlagTableArray16(flagTable, j, gSaveContext.eventChkInf[j]); - break; - case ITEM_GET_INF: - DrawFlagTableArray16(flagTable, j, gSaveContext.itemGetInf[j]); - break; - case INF_TABLE: - DrawFlagTableArray16(flagTable, j, gSaveContext.infTable[j]); - break; - case EVENT_INF: - DrawFlagTableArray16(flagTable, j, gSaveContext.eventInf[j]); - break; - case RANDOMIZER_INF: - DrawFlagTableArray16(flagTable, j, gSaveContext.ship.randomizerInf[j]); - break; - } - }, - flagTable.name); + ImGui::Text("%s", fmt::format("{:<2X}", j).c_str()); + + switch (flagTable.flagTableType) { + case EVENT_CHECK_INF: + DrawFlagTableArray16(flagTable, j, gSaveContext.eventChkInf[j]); + break; + case ITEM_GET_INF: + DrawFlagTableArray16(flagTable, j, gSaveContext.itemGetInf[j]); + break; + case INF_TABLE: + DrawFlagTableArray16(flagTable, j, gSaveContext.infTable[j]); + break; + case EVENT_INF: + DrawFlagTableArray16(flagTable, j, gSaveContext.eventInf[j]); + break; + case RANDOMIZER_INF: + DrawFlagTableArray16(flagTable, j, gSaveContext.ship.randomizerInf[j]); + break; + } + }, + flagTable.name); + } + } else { + DrawFlagTableSearchResults(flagTable, flagFilter); } // make some buttons to help with fishsanity debugging @@ -991,6 +1071,7 @@ void DrawFlagsTab() { } } + ImGui::PopID(); ImGui::TreePop(); } } diff --git a/soh/soh/Enhancements/enhancementTypes.h b/soh/soh/Enhancements/enhancementTypes.h index bd025115b90..fbdb378cc61 100644 --- a/soh/soh/Enhancements/enhancementTypes.h +++ b/soh/soh/Enhancements/enhancementTypes.h @@ -18,6 +18,7 @@ typedef enum { SGIA_DISABLED, SGIA_JUNK, SGIA_ALL, + SGIA_ADVANCED, SGIA_SIZE, } SkipGetItemAnimationType; diff --git a/soh/soh/Enhancements/game-interactor/GIEventQueue.cpp b/soh/soh/Enhancements/game-interactor/GIEventQueue.cpp new file mode 100644 index 00000000000..0872ceded31 --- /dev/null +++ b/soh/soh/Enhancements/game-interactor/GIEventQueue.cpp @@ -0,0 +1,95 @@ +#include "GameInteractor.h" +#include "soh/Enhancements/custom-item/CustomItem.h" + +extern "C" { +#include "variables.h" +#include "macros.h" +#include "functions.h" + +extern SaveContext gSaveContext; +extern PlayState* gPlayState; +} + +void ProcessEvents() { + Player* player = GET_PLAYER(gPlayState); + + // If the player has a message active, stop + if (gPlayState->msgCtx.msgMode != 0) { + return; + } + + // If the player is in a blocking cutscene, stop + if (Player_InBlockingCsMode(gPlayState, player)) { + return; + } + + // If player is dead, stop + if (player->stateFlags1 & PLAYER_STATE1_DEAD) { + return; + } + + // If there is an event active, stop + const auto& currentEvent = GameInteractor::Instance->currentEvent; + if (auto e = std::get_if(¤tEvent)) { + // no-op + } else { + return; + } + + // If there are no events that need to happen, stop + if (GameInteractor::Instance->events.empty()) { + return; + } + + GameInteractor::Instance->currentEvent = GameInteractor::Instance->events.front(); + const auto& nextEvent = GameInteractor::Instance->currentEvent; + + if (auto e = std::get_if(&nextEvent)) { + EnItem00* enItem00; + // If the player is climbing or in the air, deliver the item without a cutscene but freeze the player + if (!e->showGetItemCutscene || + (player->stateFlags1 & + (PLAYER_STATE1_CHARGING_SPIN_ATTACK | PLAYER_STATE1_HANGING_OFF_LEDGE | PLAYER_STATE1_CLIMBING_LEDGE | + PLAYER_STATE1_JUMPING | PLAYER_STATE1_FREEFALL | PLAYER_STATE1_FIRST_PERSON | + PLAYER_STATE1_CLIMBING_LADDER | PLAYER_STATE1_IN_WATER)) || + (Player_GetExplosiveHeld(player) > -1)) { + enItem00 = CustomItem::Spawn( + player->actor.world.pos.x, player->actor.world.pos.y, player->actor.world.pos.z, 0, + CustomItem::GIVE_OVERHEAD | CustomItem::HIDE_TILL_OVERHEAD | CustomItem::KEEP_ON_PLAYER, e->param, + [](Actor* actor, PlayState* play) { + Player* player = GET_PLAYER(gPlayState); + const auto& nextEvent = GameInteractor::Instance->currentEvent; + if (auto e = std::get_if(&nextEvent)) { + e->giveItem(actor, play); + if (e->showGetItemCutscene) { + player->actor.freezeTimer = 30; + } + } + }, + e->drawItem); + } else { + enItem00 = CustomItem::Spawn( + player->actor.world.pos.x, player->actor.world.pos.y, player->actor.world.pos.z, 0, + CustomItem::GIVE_ITEM_CUTSCENE | CustomItem::HIDE_TILL_OVERHEAD | CustomItem::KEEP_ON_PLAYER, e->param, + e->giveItem, e->drawItem); + } + enItem00->actor.destroy = [](Actor* actor, PlayState* play) { + if (!(CUSTOM_ITEM_FLAGS & CustomItem::CALLED_ACTION)) { + // Event was not handled, requeue it + GameInteractor::Instance->events.push_back(GameInteractor::Instance->currentEvent); + } + + GameInteractor::Instance->currentEvent = GIEventNone{}; + }; + } + + GameInteractor::Instance->events.erase(GameInteractor::Instance->events.begin()); +} + +void GameInteractor::RegisterOwnHooks() { + // Cleanup all hooks at the start of each frame + GameInteractor::Instance->RegisterGameHook( + []() { GameInteractor::Instance->RemoveAllQueuedHooks(); }); + + GameInteractor::Instance->RegisterGameHook(ProcessEvents); +} diff --git a/soh/soh/Enhancements/game-interactor/GameInteractor.h b/soh/soh/Enhancements/game-interactor/GameInteractor.h index 8ac2d9dda39..4856a41788b 100644 --- a/soh/soh/Enhancements/game-interactor/GameInteractor.h +++ b/soh/soh/Enhancements/game-interactor/GameInteractor.h @@ -68,6 +68,7 @@ typedef enum { extern "C" { #endif uint8_t GameInteractor_NoUIActive(); +void GameInteractor_SetNoUIActive(uint8_t state); GILinkSize GameInteractor_GetLinkSize(); void GameInteractor_SetLinkSize(GILinkSize size); uint8_t GameInteractor_InvisibleLinkActive(); @@ -108,6 +109,22 @@ void GameInteractor_SetTriforceHuntCreditsWarpActive(uint8_t state); #pragma message("Compiling without support, the Hook Debugger will not be available") #endif +struct GIEventNone {}; + +struct GIEventGiveItem { + // Whether or not to show the get item cutscene. If true and the player is in the air, the + // player will instead be frozen for a few seconds. If this is true you _must_ call + // CustomMessage::SetActiveCustomMessage in the giveItem function otherwise you'll just see a blank message. + bool showGetItemCutscene; + // Arbitrary s16 that can be accessed from within the give/draw functions with CUSTOM_ITEM_PARAM + s16 param; + // These are run in the context of an item00 actor. This isn't super important but can be useful in some cases + ActorFunc giveItem; + ActorFunc drawItem; +}; + +typedef std::variant GIEvent; + typedef uint32_t HOOK_ID; enum HookType { @@ -160,6 +177,33 @@ struct HookInfo { body; \ va_end(args); \ }) +#define COND_HOOK(hookType, condition, body) \ + { \ + static HOOK_ID hookId = 0; \ + GameInteractor::Instance->UnregisterGameHook(hookId); \ + hookId = 0; \ + if (condition) { \ + hookId = GameInteractor::Instance->RegisterGameHook(body); \ + } \ + } +#define COND_ID_HOOK(hookType, id, condition, body) \ + { \ + static HOOK_ID hookId = 0; \ + GameInteractor::Instance->UnregisterGameHookForID(hookId); \ + hookId = 0; \ + if (condition) { \ + hookId = GameInteractor::Instance->RegisterGameHookForID(id, body); \ + } \ + } +#define COND_VB_SHOULD(id, condition, body) \ + { \ + static HOOK_ID hookId = 0; \ + GameInteractor::Instance->UnregisterGameHookForID(hookId); \ + hookId = 0; \ + if (condition) { \ + hookId = REGISTER_VB_SHOULD(id, body); \ + } \ + } #define COND_HOOK(hookType, condition, body) \ { \ @@ -193,6 +237,8 @@ class GameInteractor { public: static GameInteractor* Instance; + void RegisterOwnHooks(); + // Game State class State { public: @@ -225,6 +271,10 @@ class GameInteractor { static GameInteractionEffectQueryResult ApplyEffect(GameInteractionEffectBase* effect); static GameInteractionEffectQueryResult RemoveEffect(RemovableGameInteractionEffect* effect); + // EventQueue + std::vector events = {}; + GIEvent currentEvent = GIEventNone(); + // Game Hooks HOOK_ID nextHookId = 1; diff --git a/soh/soh/Enhancements/game-interactor/GameInteractor_HookTable.h b/soh/soh/Enhancements/game-interactor/GameInteractor_HookTable.h index 5da302fe820..1098296764b 100644 --- a/soh/soh/Enhancements/game-interactor/GameInteractor_HookTable.h +++ b/soh/soh/Enhancements/game-interactor/GameInteractor_HookTable.h @@ -8,6 +8,7 @@ DEFINE_HOOK(OnZTitleInit, (void* gameState)); DEFINE_HOOK(OnZTitleUpdate, (void* gameState)); DEFINE_HOOK(OnLoadGame, (int32_t fileNum)); +DEFINE_HOOK(PostLoadGame, (int32_t fileNum)); DEFINE_HOOK(OnExitGame, (int32_t fileNum)); DEFINE_HOOK(OnGameStateMainStart, ()); DEFINE_HOOK(OnGameFrameUpdate, ()); @@ -16,6 +17,7 @@ DEFINE_HOOK(OnEquipmentDelete, (int16_t equipmentType, uint16_t equipValue)); DEFINE_HOOK(OnSaleEnd, (GetItemEntry itemEntry)); DEFINE_HOOK(OnTransitionEnd, (int16_t sceneNum)); DEFINE_HOOK(OnSceneInit, (int16_t sceneNum)); +DEFINE_HOOK(OnRoomInit, (int16_t roomNum)); DEFINE_HOOK(AfterSceneCommands, (int16_t sceneNum)); DEFINE_HOOK(OnSceneFlagSet, (int16_t sceneNum, int16_t flagType, int16_t flag)); DEFINE_HOOK(OnSceneFlagUnset, (int16_t sceneNum, int16_t flagType, int16_t flag)); @@ -23,6 +25,7 @@ DEFINE_HOOK(OnFlagSet, (int16_t flagType, int16_t flag)); DEFINE_HOOK(OnFlagUnset, (int16_t flagType, int16_t flag)); DEFINE_HOOK(OnSceneSpawnActors, ()); DEFINE_HOOK(OnPlayerUpdate, ()); +DEFINE_HOOK(OnPlayerDeath, ()); DEFINE_HOOK(OnSetDoAction, (uint16_t action)); DEFINE_HOOK(OnPlayerSfx, (u16 sfxId)); DEFINE_HOOK(OnOcarinaSongAction, ()); @@ -35,12 +38,14 @@ DEFINE_HOOK(OnActorInit, (void* actor)); DEFINE_HOOK(OnActorSpawn, (void* actor)); DEFINE_HOOK(ShouldActorUpdate, (void* actor, bool* result)); DEFINE_HOOK(OnActorUpdate, (void* actor)); +DEFINE_HOOK(OnActorDraw, (void* actor)); DEFINE_HOOK(OnActorKill, (void* actor)); DEFINE_HOOK(OnActorDestroy, (void* actor)); DEFINE_HOOK(OnEnemyDefeat, (void* actor)); DEFINE_HOOK(OnBossDefeat, (void* actor)); DEFINE_HOOK(OnTimestamp, (u8 item)); DEFINE_HOOK(OnPlayerBonk, ()); +DEFINE_HOOK(OnPlayerRoll, ()); DEFINE_HOOK(OnPlayerHealthChange, (int16_t amount)); DEFINE_HOOK(OnPlayerBottleUpdate, (int16_t contents)); DEFINE_HOOK(OnPlayerHoldUpShield, ()); @@ -50,15 +55,23 @@ DEFINE_HOOK(OnPlayerShieldControl, (float_t * sp50, float_t* sp54)); DEFINE_HOOK(OnPlayDestroy, ()); DEFINE_HOOK(OnPlayDrawBegin, ()); DEFINE_HOOK(OnPlayDrawEnd, ()); +DEFINE_HOOK(OnOpenText, (u16 * textId, bool* loadFromMessageTable)); DEFINE_HOOK(OnVanillaBehavior, (GIVanillaBehavior flag, bool* result, va_list originalArgs)); DEFINE_HOOK(OnSaveFile, (int32_t fileNum, int32_t sectionID)); DEFINE_HOOK(OnLoadFile, (int32_t fileNum)); +DEFINE_HOOK(OnCopyFile, (int32_t sourceFileNum, uint32_t destFileNum)); DEFINE_HOOK(OnDeleteFile, (int32_t fileNum)); +DEFINE_HOOK(OnLinkAnimEnd, (SkelAnime * skelAnime)); +DEFINE_HOOK(OnQPADamage, (uint32_t * dmgFlags)); +DEFINE_HOOK(OnESS, ()); +DEFINE_HOOK(OnWaitForPutaway, ()); +DEFINE_HOOK(OnAnimationSetLoadFrame, (LinkAnimationHeader * animation, int32_t* frame)); DEFINE_HOOK(OnDialogMessage, ()); DEFINE_HOOK(OnPresentTitleCard, ()); DEFINE_HOOK(OnInterfaceUpdate, ()); DEFINE_HOOK(OnKaleidoscopeUpdate, (int16_t inDungeonScene)); +DEFINE_HOOK(OnKaleidoMoveCursorFromSpecialPos, (PauseContext * pauseCtx, uint16_t* cursorItem)); DEFINE_HOOK(OnPresentFileSelect, ()); DEFINE_HOOK(OnUpdateFileSelectSelection, (uint16_t optionIndex)); @@ -81,6 +94,10 @@ DEFINE_HOOK(OnSetGameLanguage, ()); DEFINE_HOOK(OnAssetAltChange, ()); DEFINE_HOOK(OnKaleidoUpdate, ()); +DEFINE_HOOK(OnRandomizerItemGivenHooks, (uint32_t rc, GetItemEntry gi, uint8_t isGiSkipped)); +DEFINE_HOOK(OnArchipelagoItemReceived, (uint32_t rg)); +DEFINE_HOOK(OnRandomizerExternalCheck, (uint32_t rc)); + // Audio DEFINE_HOOK(OnSeqPlayerInit, (int32_t playerIdx, int32_t seqId)); diff --git a/soh/soh/Enhancements/game-interactor/GameInteractor_Hooks.cpp b/soh/soh/Enhancements/game-interactor/GameInteractor_Hooks.cpp index 2ffb7099eba..24d56e35f83 100644 --- a/soh/soh/Enhancements/game-interactor/GameInteractor_Hooks.cpp +++ b/soh/soh/Enhancements/game-interactor/GameInteractor_Hooks.cpp @@ -14,14 +14,15 @@ void GameInteractor_ExecuteOnLoadGame(int32_t fileNum) { GameInteractor::Instance->ExecuteHooks(fileNum); } +void GameInteractor_ExecutePostLoadGame(int32_t fileNum) { + GameInteractor::Instance->ExecuteHooks(fileNum); +} + void GameInteractor_ExecuteOnExitGame(int32_t fileNum) { GameInteractor::Instance->ExecuteHooks(fileNum); } void GameInteractor_ExecuteOnGameStateMainStart() { - // Cleanup all hooks at the start of each frame - GameInteractor::Instance->RemoveAllQueuedHooks(); - GameInteractor::Instance->ExecuteHooks(); } @@ -56,6 +57,12 @@ void GameInteractor_ExecuteOnSceneInit(int16_t sceneNum) { GameInteractor::Instance->ExecuteHooksForFilter(sceneNum); } +void GameInteractor_ExecuteOnRoomInit(int16_t roomNum) { + GameInteractor::Instance->ExecuteHooks(roomNum); + GameInteractor::Instance->ExecuteHooksForID(roomNum, roomNum); + GameInteractor::Instance->ExecuteHooksForFilter(roomNum); +} + void GameInteractor_ExecuteAfterSceneCommands(int16_t sceneNum) { GameInteractor::Instance->ExecuteHooks(sceneNum); GameInteractor::Instance->ExecuteHooksForID(sceneNum, sceneNum); @@ -90,6 +97,10 @@ void GameInteractor_ExecuteOnPlayerUpdate() { GameInteractor::Instance->ExecuteHooks(); } +void GameInteractor_ExecuteOnPlayerDeath() { + GameInteractor::Instance->ExecuteHooks(); +} + void GameInteractor_ExecuteOnSetDoAction(uint16_t action) { GameInteractor::Instance->ExecuteHooks(action); } @@ -110,6 +121,26 @@ void GameInteractor_ExecuteOnCuccoOrChickenHatch() { GameInteractor::Instance->ExecuteHooks(); } +void GameInteractor_ExecuteOnLinkAnimEnd(SkelAnime* skelAnime) { + GameInteractor::Instance->ExecuteHooks(skelAnime); +} + +void GameInteractor_ExecuteOnQPADamage(uint32_t* dmgFlags) { + GameInteractor::Instance->ExecuteHooks(dmgFlags); +} + +void GameInteractor_ExecuteOnESS() { + GameInteractor::Instance->ExecuteHooks(); +} + +void GameInteractor_ExecuteOnWaitForPutaway() { + GameInteractor::Instance->ExecuteHooks(); +} + +void GameInteractor_ExecuteOnAnimationSetLoadFrame(LinkAnimationHeader* animation, int32_t* frame) { + GameInteractor::Instance->ExecuteHooks(animation, frame); +} + void GameInteractor_ExecuteOnShopSlotChangeHooks(uint8_t cursorIndex, int16_t price) { GameInteractor::Instance->ExecuteHooks(cursorIndex, price); } @@ -149,7 +180,6 @@ bool GameInteractor_ShouldActorUpdate(void* actor) { GameInteractor::Instance->ExecuteHooksForFilter(actor, &result); return result; } - void GameInteractor_ExecuteOnActorUpdate(void* actor) { GameInteractor::Instance->ExecuteHooks(actor); GameInteractor::Instance->ExecuteHooksForID(((Actor*)actor)->id, actor); @@ -157,6 +187,13 @@ void GameInteractor_ExecuteOnActorUpdate(void* actor) { GameInteractor::Instance->ExecuteHooksForFilter(actor); } +void GameInteractor_ExecuteOnActorDraw(void* actor) { + GameInteractor::Instance->ExecuteHooks(actor); + GameInteractor::Instance->ExecuteHooksForID(((Actor*)actor)->id, actor); + GameInteractor::Instance->ExecuteHooksForPtr((uintptr_t)actor, actor); + GameInteractor::Instance->ExecuteHooksForFilter(actor); +} + void GameInteractor_ExecuteOnActorKill(void* actor) { GameInteractor::Instance->ExecuteHooks(actor); GameInteractor::Instance->ExecuteHooksForID(((Actor*)actor)->id, actor); @@ -193,6 +230,10 @@ void GameInteractor_ExecuteOnPlayerBonk() { GameInteractor::Instance->ExecuteHooks(); } +void GameInteractor_ExecuteOnPlayerRoll() { + GameInteractor::Instance->ExecuteHooks(); +} + void GameInteractor_ExecuteOnPlayerHealthChange(int16_t amount) { GameInteractor::Instance->ExecuteHooks(amount); } @@ -229,6 +270,12 @@ void GameInteractor_ExecuteOnPlayDrawEnd() { GameInteractor::Instance->ExecuteHooks(); } +void GameInteractor_ExecuteOnOpenText(u16* textId, bool* loadFromMessageTable) { + GameInteractor::Instance->ExecuteHooks(textId, loadFromMessageTable); + GameInteractor::Instance->ExecuteHooksForID(*textId, textId, loadFromMessageTable); + GameInteractor::Instance->ExecuteHooksForFilter(textId, loadFromMessageTable); +} + bool GameInteractor_Should(GIVanillaBehavior flag, u32 result, ...) { // Only the external function can use the Variadic Function syntax // To pass the va args to the next caller must be done using va_list and reading the args into it @@ -260,6 +307,10 @@ void GameInteractor_ExecuteOnLoadFile(int32_t fileNum) { GameInteractor::Instance->ExecuteHooks(fileNum); } +void GameInteractor_ExecuteOnCopyFile(int32_t sourceFileNum, int32_t destFileNum) { + GameInteractor::Instance->ExecuteHooks(sourceFileNum, destFileNum); +} + void GameInteractor_ExecuteOnDeleteFile(int32_t fileNum) { GameInteractor::Instance->ExecuteHooks(fileNum); } @@ -282,6 +333,10 @@ void GameInteractor_ExecuteOnKaleidoscopeUpdate(int16_t inDungeonScene) { GameInteractor::Instance->ExecuteHooks(inDungeonScene); } +void GameInteractor_ExecuteOnKaleidoMoveCursorFromSpecialPos(PauseContext* pauseCtx, uint16_t* cursorItem) { + GameInteractor::Instance->ExecuteHooks(pauseCtx, cursorItem); +} + // MARK: - Main Menu void GameInteractor_ExecuteOnPresentFileSelect() { @@ -363,6 +418,20 @@ void GameInteractor_ExecuteOnKaleidoUpdate() { GameInteractor::Instance->ExecuteHooks(); } +// Mark: Randomizer +void GameInteractor_ExecuteOnRandomizerItemGivenHooks(uint32_t rc, GetItemEntry gi, uint8_t isGiSkipped) { + GameInteractor::Instance->ExecuteHooks(rc, gi, isGiSkipped); +} + +// MARK: Archipelago +void GameInteractor_ExecuteOnArchipelagoItemReceived(uint32_t rg) { + GameInteractor::Instance->ExecuteHooks(rg); +} + +void GameInteractor_ExecuteOnRandomizerExternalCheck(uint32_t rc) { + GameInteractor::Instance->ExecuteHooks(rc); +} + // Mark: Audio void GameInteractor_ExecuteOnSeqPlayerInit(int32_t playerIdx, int32_t seqId) { GameInteractor::Instance->ExecuteHooks(playerIdx, seqId); diff --git a/soh/soh/Enhancements/game-interactor/GameInteractor_Hooks.h b/soh/soh/Enhancements/game-interactor/GameInteractor_Hooks.h index fe3533f7376..71ba657a8af 100644 --- a/soh/soh/Enhancements/game-interactor/GameInteractor_Hooks.h +++ b/soh/soh/Enhancements/game-interactor/GameInteractor_Hooks.h @@ -11,6 +11,7 @@ extern "C" { void GameInteractor_ExecuteOnZTitleInit(void* gameState); void GameInteractor_ExecuteOnZTitleUpdate(void* gameState); void GameInteractor_ExecuteOnLoadGame(int32_t fileNum); +void GameInteractor_ExecutePostLoadGame(int32_t fileNum); void GameInteractor_ExecuteOnExitGame(int32_t fileNum); void GameInteractor_ExecuteOnGameStateMainStart(); void GameInteractor_ExecuteOnGameFrameUpdate(); @@ -19,6 +20,7 @@ void GameInteractor_ExecuteOnEquipmentDelete(int16_t equipmentType, uint16_t equ void GameInteractor_ExecuteOnSaleEndHooks(GetItemEntry itemEntry); void GameInteractor_ExecuteOnTransitionEndHooks(int16_t sceneNum); void GameInteractor_ExecuteOnSceneInit(int16_t sceneNum); +void GameInteractor_ExecuteOnRoomInit(int16_t roomNum); void GameInteractor_ExecuteAfterSceneCommands(int16_t sceneNum); void GameInteractor_ExecuteOnSceneFlagSet(int16_t sceneNum, int16_t flagType, int16_t flag); void GameInteractor_ExecuteOnSceneFlagUnset(int16_t sceneNum, int16_t flagType, int16_t flag); @@ -26,22 +28,30 @@ void GameInteractor_ExecuteOnFlagSet(int16_t flagType, int16_t flag); void GameInteractor_ExecuteOnFlagUnset(int16_t flagType, int16_t flag); void GameInteractor_ExecuteOnSceneSpawnActors(); void GameInteractor_ExecuteOnPlayerUpdate(); +void GameInteractor_ExecuteOnPlayerDeath(); void GameInteractor_ExecuteOnSetDoAction(uint16_t action); void GameInteractor_ExecuteOnPlayerSfx(u16 sfxId); void GameInteractor_ExecuteOnOcarinaSongAction(); void GameInteractor_ExecuteOnOcarinaNote(uint8_t note, float modulator, int8_t bend); void GameInteractor_ExecuteOnCuccoOrChickenHatch(); +void GameInteractor_ExecuteOnLinkAnimEnd(SkelAnime* skelAnime); +void GameInteractor_ExecuteOnQPADamage(uint32_t* dmgFlags); +void GameInteractor_ExecuteOnESS(); +void GameInteractor_ExecuteOnWaitForPutaway(); +void GameInteractor_ExecuteOnAnimationSetLoadFrame(LinkAnimationHeader* animation, int32_t* frame); bool GameInteractor_ShouldActorInit(void* actor); void GameInteractor_ExecuteOnActorInit(void* actor); void GameInteractor_ExecuteOnActorSpawn(void* actor); bool GameInteractor_ShouldActorUpdate(void* actor); void GameInteractor_ExecuteOnActorUpdate(void* actor); +void GameInteractor_ExecuteOnActorDraw(void* actor); void GameInteractor_ExecuteOnActorKill(void* actor); void GameInteractor_ExecuteOnActorDestroy(void* actor); void GameInteractor_ExecuteOnEnemyDefeat(void* actor); void GameInteractor_ExecuteOnBossDefeat(void* actor); void GameInteractor_ExecuteOnTimestamp(u8 item); void GameInteractor_ExecuteOnPlayerBonk(); +void GameInteractor_ExecuteOnPlayerRoll(); void GameInteractor_ExecuteOnPlayerHealthChange(int16_t amount); void GameInteractor_ExecuteOnPlayerBottleUpdate(int16_t contents); void GameInteractor_ExecuteOnPlayerHoldUpShield(); @@ -53,11 +63,13 @@ void GameInteractor_ExecuteOnDungeonKeyUsedHooks(uint16_t mapIndex); void GameInteractor_ExecuteOnPlayDestroy(); void GameInteractor_ExecuteOnPlayDrawBegin(); void GameInteractor_ExecuteOnPlayDrawEnd(); +void GameInteractor_ExecuteOnOpenText(u16* textId, bool* loadFromMessageTable); bool GameInteractor_Should(GIVanillaBehavior flag, uint32_t result, ...); // MARK: - Save Files void GameInteractor_ExecuteOnSaveFile(int32_t fileNum, int32_t sectionID); void GameInteractor_ExecuteOnLoadFile(int32_t fileNum); +void GameInteractor_ExecuteOnCopyFile(int32_t sourceFileNum, int32_t destFileNum); void GameInteractor_ExecuteOnDeleteFile(int32_t fileNum); // MARK: - Dialog @@ -91,6 +103,14 @@ void GameInteractor_RegisterOnAssetAltChange(void (*fn)(void)); // Mark: - Pause Menu void GameInteractor_ExecuteOnKaleidoUpdate(); +void GameInteractor_ExecuteOnKaleidoMoveCursorFromSpecialPos(PauseContext* pauseCtx, uint16_t* cursorItem); + +// Mark: - Randomizer +void GameInteractor_ExecuteOnRandomizerItemGivenHooks(uint32_t rc, GetItemEntry gi, uint8_t isGiSkipped); + +// Mark: - Archipelago +void GameInteractor_ExecuteOnArchipelagoItemReceived(uint32_t rg); +void GameInteractor_ExecuteOnRandomizerExternalCheck(uint32_t rc); // Mark: - Audio void GameInteractor_ExecuteOnSeqPlayerInit(int32_t playerIdx, int32_t seqId); diff --git a/soh/soh/Enhancements/game-interactor/GameInteractor_State.cpp b/soh/soh/Enhancements/game-interactor/GameInteractor_State.cpp index bdcb4f37a79..c6d751395a1 100644 --- a/soh/soh/Enhancements/game-interactor/GameInteractor_State.cpp +++ b/soh/soh/Enhancements/game-interactor/GameInteractor_State.cpp @@ -36,6 +36,10 @@ uint8_t GameInteractor_NoUIActive() { return GameInteractor::State::NoUIActive; } +void GameInteractor_SetNoUIActive(uint8_t state) { + GameInteractor::State::NoUIActive = state; +} + // MARK: - GameInteractor::State::LinkSize GILinkSize GameInteractor_GetLinkSize() { return GameInteractor::State::LinkSize; diff --git a/soh/soh/Enhancements/game-interactor/vanilla-behavior/GIVanillaBehavior.h b/soh/soh/Enhancements/game-interactor/vanilla-behavior/GIVanillaBehavior.h index f604c9c7954..f5d9543673e 100644 --- a/soh/soh/Enhancements/game-interactor/vanilla-behavior/GIVanillaBehavior.h +++ b/soh/soh/Enhancements/game-interactor/vanilla-behavior/GIVanillaBehavior.h @@ -502,6 +502,14 @@ typedef enum { // - None VB_END_GERUDO_MEMBERSHIP_TALK, + // #### `result` + // ```c + // true + // ``` + // #### `args` + // - `*EnArrow` + VB_EN_ARROW_MAGIC_CONSUMPTION, + // #### `result` // ```c // !(this->stateFlags3 & PLAYER_STATE3_PAUSE_ACTION_FUNC) @@ -583,6 +591,14 @@ typedef enum { // - None VB_GANON_HEAL_BEFORE_FIGHT, + // #### `result` + // ```c + // true + // ``` + // #### `args` + // - None + VB_GANONDORF_DECIDE_TO_FIGHT, + // #### `result` // ```c // true @@ -1745,6 +1761,16 @@ typedef enum { // - `*DemoIm` VB_PLAY_ZELDAS_LULLABY_CS, + // #### `result` + // ```c + // true + // ``` + // #### `args` + // - `*Player` + // - `int32_t` (magicArrowType) + // - `*int32_t` (arrowType) + VB_PLAYER_ARROW_MAGIC_CONSUMPTION, + // #### `result` // ```c // item == ITEM_SAW @@ -1761,6 +1787,14 @@ typedef enum { // - `*ObjTsubo` VB_POT_DROP_ITEM, + // #### `result` + // ```c + // actor.params & 1) == ROCK_SMALL + // ``` + // #### `args` + // - `*EnIshi` + VB_ROCK_DROP_ITEM, + // #### `result` // ```c // true @@ -2252,6 +2286,16 @@ typedef enum { // - `*Actor` VB_RECIEVE_FALL_DAMAGE, + // #### `result` + // ```c + // true + // ``` + // #### `args` + // - `*Actor` + // - `u8` (damageEffect) + // - `u8` (damage) + VB_APPLY_DAMAGE_TO_ACTOR, + // #### `result` // ```c // true @@ -2304,6 +2348,22 @@ typedef enum { // - `*DoorShutter` VB_BE_NEAR_DOOR_SHUTTER, + // #### `result` + // ```c + // false + // ``` + // #### `args` + // - None + VB_SKIP_FORCE_PLAY_OCARINA, + + // #### `result` + // ```c + // true + // ``` + // #### `args` + // - None + VB_HOVER_WITH_ISG, + // #### `result` // ```c // CVarGetInteger(CVAR_ENHANCEMENT("3DSceneRender"), 0) @@ -2382,6 +2442,18 @@ typedef enum { // - `*Color_RGB8` VB_APPLY_TUNIC_COLOR, + // #### `result` + // ```c + // true + // ``` + // #### `args` + // - `*int32_t` // ItemID + VB_USE_ITEM, + + VB_DRAW_SKEL_LIMB, + VB_DRAW_SKEL_FLEX_LIMB, + VB_PLAYER_ROLL, + } GIVanillaBehavior; #endif diff --git a/soh/soh/Enhancements/mods.cpp b/soh/soh/Enhancements/mods.cpp index 3e724c54f9b..f9e54dbbac7 100644 --- a/soh/soh/Enhancements/mods.cpp +++ b/soh/soh/Enhancements/mods.cpp @@ -7,7 +7,10 @@ #include "soh/Enhancements/enhancementTypes.h" #include #include "soh/Enhancements/timesaver_hook_handlers.h" +#include "soh/Enhancements/randomizer/3drando/random.hpp" #include "soh/Enhancements/randomizer/hook_handlers.h" +#include "soh/Enhancements/Holiday/Holiday.hpp" +#include "soh/Enhancements/randomizer/randomizer.h" #include "src/overlays/actors/ovl_En_Bb/z_en_bb.h" #include "src/overlays/actors/ovl_En_Dekubaba/z_en_dekubaba.h" @@ -22,7 +25,6 @@ #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_Door_Shutter/z_door_shutter.h" #include "src/overlays/actors/ovl_Door_Gerudo/z_door_gerudo.h" #include "src/overlays/actors/ovl_En_Elf/z_en_elf.h" #include "soh_assets.h" @@ -35,9 +37,12 @@ extern "C" { #include "soh/cvar_prefixes.h" #include "variables.h" #include "functions.h" +#include "src/overlays/actors/ovl_En_Door/z_en_door.h" +#include "src/overlays/actors/ovl_Door_Shutter/z_door_shutter.h" extern SaveContext gSaveContext; extern PlayState* gPlayState; +extern s16 gEnSnowballId; } /// Switches Link's age and respawns him at the last entrance he entered. @@ -442,6 +447,212 @@ void RegisterRandomizedEnemySizes() { }); } +static CollisionPoly snowballPoly; +static Vec3f snowballPos; +static f32 raycastResult; + +static u32 iceBlockParams[] = { + 0x214, 0x1, 0x11, 0x10, 0x20, +}; + +void RegisterSnowballs() { + GameInteractor::Instance->RegisterGameHook([]() { + if (gPlayState->sceneNum != SCENE_HYRULE_FIELD && gPlayState->sceneNum != SCENE_KAKARIKO_VILLAGE) { + return; + } + + int actorsSpawned = 0; + + while (actorsSpawned < 30) { + snowballPos.x = (float)(Random((gPlayState->sceneNum == SCENE_HYRULE_FIELD ? -10000 : -2700) + 10000, + (gPlayState->sceneNum == SCENE_HYRULE_FIELD ? 5000 : 2000) + 10000) - + (float)10000.0f); + snowballPos.y = 5000; + snowballPos.z = (float)(Random((gPlayState->sceneNum == SCENE_HYRULE_FIELD ? -1000 : -2000) + 10000, + (gPlayState->sceneNum == SCENE_HYRULE_FIELD ? 15000 : 2000) + 10000) - + (float)10000.0f); + + raycastResult = BgCheck_AnyRaycastFloor1(&gPlayState->colCtx, &snowballPoly, &snowballPos); + + if (raycastResult > BGCHECK_Y_MIN) { + Actor_Spawn(&gPlayState->actorCtx, gPlayState, gEnSnowballId, snowballPos.x, raycastResult, + snowballPos.z, 0, 0, 0, gPlayState->sceneNum == SCENE_HYRULE_FIELD, 0); + actorsSpawned++; + } + } + }); + + GameInteractor::Instance->RegisterGameHook([]() { + if (gPlayState->sceneNum != SCENE_LAKE_HYLIA) { + return; + } + + int actorsSpawned = 0; + + Vec3f spawnedIceBlockPos[15]; + + while (actorsSpawned < 15) { + Vec3f iceBlockPos; + iceBlockPos.x = (float)(Random((-4200) + 10000, (3000) + 10000) - (float)10000.0f); + iceBlockPos.y = -1713.0f; + iceBlockPos.z = (float)(Random((2600) + 10000, (9000) + 10000) - (float)10000.0f); + + raycastResult = BgCheck_AnyRaycastFloor1(&gPlayState->colCtx, &snowballPoly, &iceBlockPos); + + if (raycastResult > BGCHECK_Y_MIN) { + + bool overlaps = false; + for (int i = 0; i < actorsSpawned; i++) { + if (Math_Vec3f_DistXZ(&spawnedIceBlockPos[i], &iceBlockPos) < 500.0f) { + overlaps = true; + break; + } + } + + if (overlaps) { + continue; + } + + if (LINK_IS_ADULT && !Flags_GetEventChkInf(EVENTCHKINF_RAISED_LAKE_HYLIA_WATER)) { + iceBlockPos.y = raycastResult; + } else { + iceBlockPos.y = -1310.0f; + } + + Actor_Spawn(&gPlayState->actorCtx, gPlayState, ACTOR_BG_SPOT08_ICEBLOCK, iceBlockPos.x, iceBlockPos.y, + iceBlockPos.z, 0, (s16)Random(0, 0xFFFF), 0, RandomElement(iceBlockParams), 0); + spawnedIceBlockPos[actorsSpawned] = iceBlockPos; + actorsSpawned++; + } + } + }); +} + +#define DOOR_TYPE_MASK 0x0380 +#define SWITCH_FLAG_MASK 0x003F + +typedef struct { + std::vector DoorsVector; + std::set DoorsSet; + std::unordered_map LockedDoors; +} SceneDoor; + +std::unordered_map SceneDoors = { + { SCENE_FOREST_TEMPLE, + { { 63, 1087, 2111, 3135, 4159, 5824, 8319, 9343, 10367, 12415, 13439, 14465, 15423, 17471, 23615, 26306, 27331, + 28356 } } }, + { SCENE_FIRE_TEMPLE, { { -28515, -30569, 17471, 16447, 13375, 15423, 14494, 6296, 27711, 18495, 21659, 19610, + 28735, 22591, 23615, 5279, 7231, -31681, 9369, 8255, 11327, 10303, 12351 } } }, + { SCENE_WATER_TEMPLE, { { 8325, 9345, 10370, 11327, 13439, 14470, 16447, 18559, 20543, 21641 } } }, + { SCENE_SHADOW_TEMPLE, + { { 127, 1151, 2111, 3225, 4246, 5183, 6271, 7231, 8343, 9368, 11327, 12351, 13439, 14399, 15487, 16511, + 17557 } } }, + { SCENE_SPIRIT_TEMPLE, + { { 63, 3789, 6234, 7257, 9950, 10367, 11391, 13375, 15068, 16474, 18559, 19583, 21211, 22229, 26751 } } }, + { SCENE_BOTTOM_OF_THE_WELL, { { 1087, 2203, 3229, 4252, 5183, 6271 } } }, + { SCENE_GERUDO_TRAINING_GROUND, + { { 4133, 16074, 17091, 15041, 14025, 18116, 19141, 20166, 21191, 22231, 5183, 6207, 7231, 9294, 8345 } } }, + { SCENE_INSIDE_GANONS_CASTLE, { { 2111, 3806, 8255, 9279, 10303, 11327, 12351, 16447, 20189 } } } +}; + +// Rando option-driven Random Locked Doors + +static void OnRandomLockedDoorsLoadGame(int32_t fileNum) { + // Only do anything if the rando option is on + if (!IS_RANDO || !RAND_GET_OPTION(RSK_RANDOM_LOCKED_DOORS)) { + return; + } + + for (auto& scene : SceneDoors) { + scene.second.LockedDoors.clear(); + + uint32_t finalSeed = + scene.first + (IS_RANDO ? Rando::Context::GetInstance()->GetSeed() : gSaveContext.ship.stats.fileCreatedAt); + + Random_Init(finalSeed); + + std::vector Flags; + for (auto& door : scene.second.DoorsVector) { + scene.second.DoorsSet.insert(door); + + if (((door >> 7 & 7) == DOOR_LOCKED) || ((door >> 6 & 0xF) == SHUTTER_KEY_LOCKED)) { + Flags.push_back(door & SWITCH_FLAG_MASK); + } + } + + while (!Flags.empty()) { + s32 RandomDoor = RandomElement(scene.second.DoorsVector); + if (!scene.second.LockedDoors.contains(RandomDoor)) { + scene.second.LockedDoors[RandomDoor] = Flags.back(); + Flags.pop_back(); + } + } + } +} + +static void OnRandomLockedDoorsActorInit(void* refActor) { + if (!IS_RANDO || !RAND_GET_OPTION(RSK_RANDOM_LOCKED_DOORS)) { + return; + } + + Actor* actor = static_cast(refActor); + + if (actor->id != ACTOR_EN_DOOR && actor->id != ACTOR_DOOR_SHUTTER) { + return; + } + if (!SceneDoors.contains(gPlayState->sceneNum)) { + return; + } + + s32 DoorParamsCopy = actor->params; + + auto& sceneInfo = SceneDoors[gPlayState->sceneNum]; + + if (sceneInfo.LockedDoors.contains(actor->params)) { + actor->params = (actor->params & ~DOOR_TYPE_MASK) | (DOOR_LOCKED << 7); + actor->params &= ~SWITCH_FLAG_MASK; + actor->params |= (sceneInfo.LockedDoors[DoorParamsCopy] & SWITCH_FLAG_MASK); + + if (actor->id == ACTOR_EN_DOOR) { + EnDoor* doorActor = static_cast(refActor); + doorActor->actionFunc = EnDoor_SetupType; + } else { + DoorShutter* shutterActor = static_cast(refActor); + shutterActor->actionFunc = DoorShutter_SetupType; + shutterActor->doorType = SHUTTER_KEY_LOCKED; + shutterActor->unk_16F = 0; + + if (!Flags_GetSwitch(gPlayState, shutterActor->dyna.actor.params & 0x3F)) { + shutterActor->unk_16E = 10; + } else { + shutterActor->unk_16E = 0; + } + } + } else if (sceneInfo.DoorsSet.contains(actor->params)) { + actor->params = (actor->params & ~DOOR_TYPE_MASK) | (DOOR_ROOMLOAD << 7); + + if (actor->id == ACTOR_EN_DOOR) { + EnDoor* doorActor = static_cast(refActor); + doorActor->actionFunc = EnDoor_SetupType; + } else { + DoorShutter* shutterActor = static_cast(refActor); + shutterActor->actionFunc = DoorShutter_SetupType; + shutterActor->doorType = DOOR_ROOMLOAD; + shutterActor->unk_16F = 0; + shutterActor->unk_16E = 0; + } + } +} + +void RegisterRandomLockedDoors() { + bool shouldRegister = IS_RANDO && RAND_GET_OPTION(RSK_RANDOM_LOCKED_DOORS); + + COND_HOOK(OnLoadGame, shouldRegister, OnRandomLockedDoorsLoadGame); + COND_HOOK(OnActorInit, shouldRegister, OnRandomLockedDoorsActorInit); +} + +static RegisterShipInitFunc initFunc(RegisterRandomLockedDoors, { "IS_RANDO" }); + void InitMods() { RandomizerRegisterHooks(); TimeSaverRegisterHooks(); diff --git a/soh/soh/Enhancements/randomizer/3drando/hint_list/hint_list_item.cpp b/soh/soh/Enhancements/randomizer/3drando/hint_list/hint_list_item.cpp index c74c0ff23b9..8c1f026da22 100644 --- a/soh/soh/Enhancements/randomizer/3drando/hint_list/hint_list_item.cpp +++ b/soh/soh/Enhancements/randomizer/3drando/hint_list/hint_list_item.cpp @@ -2132,6 +2132,80 @@ void StaticData::HintTable_Init_Item() { CustomMessage("a four legged friend", /*german*/"ein vierbeiniger Freund", /*french*/"un puissant animal")}); // /*spanish*/una amiga cuadrúpeda + hintTextTable[RHT_ABILITY_ISG] = HintText(CustomMessage("ISG", /*german*/"ISG", /*french*/"ISG"), + // /*spanish*/ISG + { + CustomMessage("a flashy weapon", /*german*/"ISG", /*french*/"ISG") + // /*spanish*/ISG + }, { + CustomMessage("a permanent hitbox", /*german*/"ISG", /*french*/"ISG")}); + // /*spanish*/ISG + + hintTextTable[RHT_ABILITY_OI] = HintText(CustomMessage("OI", /*german*/"OI", /*french*/"OI"), + // /*spanish*/OI + { + CustomMessage("a musical bottle", /*german*/"OI", /*french*/"OI") + // /*spanish*/OI + }, { + CustomMessage("a potato of any color", /*german*/"OI", /*french*/"OI")}); + // /*spanish*/OI + hintTextTable[RHT_ABILITY_QPA] = HintText(CustomMessage("QPA", /*german*/"QPA", /*french*/"QPA"), + // /*spanish*/QPA + { + CustomMessage("some funky damage", /*german*/"QPA", /*french*/"QPA") + // /*spanish*/QPA + }, { + CustomMessage("some fast hands", /*german*/"QPA", /*french*/"QPA")}); + // /*spanish*/QPA + hintTextTable[RHT_ABILITY_HESS] = HintText(CustomMessage("Extended Superslide", /*german*/"Extended Superslide", /*french*/"Extended Superslide"), + // /*spanish*/Extended Superslide + { + CustomMessage("some explosive speed", /*german*/"Extended Superslide", /*french*/"Extended Superslide") + // /*spanish*/Extended Superslide + }, { + CustomMessage("some slick feet", /*german*/"Extended Superslide", /*french*/"Extended Superslide")}); + // /*spanish*/Extended Superslide + hintTextTable[RHT_ABILITY_SUPERSLIDE] = HintText(CustomMessage("Superslide", /*german*/"Superslide", /*french*/"Superslide"), + // /*spanish*/Superslide + { + CustomMessage("some straight line speed", /*german*/"Superslide", /*french*/"Superslide") + // /*spanish*/Superslide + }, { + CustomMessage("some shield power", /*german*/"Superslide", /*french*/"Superslide")}); + // /*spanish*/Superslide + hintTextTable[RHT_ABILITY_HOVER] = HintText(CustomMessage("Hovering", /*german*/"Hovering", /*french*/"Hovering"), + // /*spanish*/Hovering + { + CustomMessage("a weightless trick", /*german*/"Hovering", /*french*/"Hovering") + // /*spanish*/Hovering + }, { + CustomMessage("gravityn't", /*german*/"Hovering", /*french*/"Hovering")}); + // /*spanish*/Hovering + hintTextTable[RHT_ABILITY_EQUIP_SWAP] = HintText(CustomMessage("Equip Swap", /*german*/"Equip Swap", /*french*/"Equip Swap"), + // /*spanish*/Equip Swap + { + CustomMessage("an unbufferable ability", /*german*/"Equip Swap", /*french*/"Equip Swap") + // /*spanish*/Equip Swap + }, { + CustomMessage("a timeless talent", /*german*/"Equip Swap", /*french*/"Equip Swap")}); + // /*spanish*/Equip Swap + hintTextTable[RHT_ABILITY_GROUND_JUMP] = HintText(CustomMessage("Ground Jump", /*german*/"Ground Jump", /*french*/"Ground Jump"), + // /*spanish*/Ground Jump + { + CustomMessage("a little leap", /*german*/"Ground Jump", /*french*/"Ground Jump") + // /*spanish*/Ground Jump + }, { + CustomMessage("a beginner's trick", /*german*/"Ground Jump", /*french*/"Ground Jump")}); + // /*spanish*/Ground Jump + hintTextTable[RHT_ABILITY_WEIRDSHOT] = HintText(CustomMessage("Weirdshot", /*german*/"Weirdshot", /*french*/"Weirdshot"), + // /*spanish*/Weirdshot + { + CustomMessage("a weird shot", /*german*/"Weirdshot", /*french*/"Weirdshot") + // /*spanish*/Weirdshot + }, { + CustomMessage("a mangled animation", /*german*/"Weirdshot", /*french*/"Weirdshot")}); + // /*spanish*/Weirdshot + //What is this used for? hintTextTable[RHT_HINT_MYSTERIOUS] = HintText(CustomMessage("something mysterious", /*german*/"etwas Mysteriöses", /*french*/"un sacré mystère")); // /*spanish*/algo misterioso diff --git a/soh/soh/Enhancements/randomizer/3drando/item_pool.cpp b/soh/soh/Enhancements/randomizer/3drando/item_pool.cpp index ec90b0619a8..e6cef395c54 100644 --- a/soh/soh/Enhancements/randomizer/3drando/item_pool.cpp +++ b/soh/soh/Enhancements/randomizer/3drando/item_pool.cpp @@ -82,10 +82,11 @@ const std::array alwaysItems = { RG_ARROWS_10, RG_TREASURE_GAME_HEART, }; -const std::array easyItems = { +const std::array easyItems = { RG_BIGGORON_SWORD, RG_KOKIRI_SWORD, RG_MASTER_SWORD, + RG_HYLIAN_SHIELD, RG_BOOMERANG, RG_LENS_OF_TRUTH, RG_MEGATON_HAMMER, @@ -95,8 +96,11 @@ const std::array easyItems = { RG_HOVER_BOOTS, RG_MIRROR_SHIELD, RG_FIRE_ARROWS, + RG_ICE_ARROWS, RG_LIGHT_ARROWS, RG_DINS_FIRE, + RG_FARORES_WIND, + RG_NAYRUS_LOVE, RG_PROGRESSIVE_HOOKSHOT, RG_PROGRESSIVE_STRENGTH, RG_PROGRESSIVE_SCALE, @@ -447,32 +451,45 @@ void GenerateItemPool() { ItemPool.clear(); PendingJunkPool.clear(); - // Initialize ice trap models to always major items - ctx->possibleIceTrapModels = { - RG_MIRROR_SHIELD, - RG_BOOMERANG, - RG_LENS_OF_TRUTH, - RG_MEGATON_HAMMER, - RG_IRON_BOOTS, - RG_HOVER_BOOTS, - RG_STONE_OF_AGONY, - RG_DINS_FIRE, - RG_FARORES_WIND, - RG_NAYRUS_LOVE, - RG_FIRE_ARROWS, - RG_ICE_ARROWS, - RG_LIGHT_ARROWS, - RG_DOUBLE_DEFENSE, - RG_CLAIM_CHECK, - RG_PROGRESSIVE_HOOKSHOT, - RG_PROGRESSIVE_STRENGTH, - RG_PROGRESSIVE_BOMB_BAG, - RG_PROGRESSIVE_BOW, - RG_PROGRESSIVE_SLINGSHOT, - RG_PROGRESSIVE_WALLET, - RG_PROGRESSIVE_SCALE, - RG_PROGRESSIVE_MAGIC_METER, + const bool plentiful = ctx->GetOption(RSK_ITEM_POOL).Is(RO_ITEM_POOL_PLENTIFUL); + + auto AddPlentifulCopy = [&](auto item) { + if (plentiful) { + AddItemToPool(PendingJunkPool, item); + } }; + + // Initialize ice trap models that are always in the pool + ctx->possibleIceTrapModels = { RG_MIRROR_SHIELD, + RG_BOOMERANG, + RG_LENS_OF_TRUTH, + RG_MEGATON_HAMMER, + RG_IRON_BOOTS, + RG_HOVER_BOOTS, + RG_STONE_OF_AGONY, + RG_DINS_FIRE, + RG_FARORES_WIND, + RG_NAYRUS_LOVE, + RG_FIRE_ARROWS, + RG_ICE_ARROWS, + RG_LIGHT_ARROWS, + RG_DOUBLE_DEFENSE, + RG_CLAIM_CHECK, + RG_PROGRESSIVE_HOOKSHOT, + RG_PROGRESSIVE_STRENGTH, + RG_PROGRESSIVE_BOMB_BAG, + RG_PROGRESSIVE_BOW, + RG_PROGRESSIVE_SLINGSHOT, + RG_PROGRESSIVE_WALLET, + RG_PROGRESSIVE_SCALE, + RG_PROGRESSIVE_MAGIC_METER, + RG_HYLIAN_SHIELD, + RG_GREG_RUPEE, + RG_GREEN_RUPEE, + RG_RED_RUPEE, + RG_PURPLE_RUPEE, + RG_HUGE_RUPEE, + RG_RECOVERY_HEART }; // Check song shuffle and dungeon reward shuffle just for ice traps if (ctx->GetOption(RSK_SHUFFLE_SONGS).Is(RO_SONG_SHUFFLE_ANYWHERE)) { // Push item ids for songs @@ -572,12 +589,60 @@ void GenerateItemPool() { if (ctx->GetOption(RSK_SKELETON_KEY)) { AddItemToMainPool(RG_SKELETON_KEY); + AddPlentifulCopy(RG_SKELETON_KEY); + ctx->possibleIceTrapModels.push_back(RG_SKELETON_KEY); } if (ctx->GetOption(RSK_SHUFFLE_SWIM)) { AddItemToMainPool(RG_PROGRESSIVE_SCALE); } + if (ctx->GetOption(RSK_SHUFFLE_ISG)) { + AddItemToMainPool(RG_ABILITY_ISG); + AddPlentifulCopy(RG_ABILITY_ISG); + ctx->possibleIceTrapModels.push_back(RG_ABILITY_ISG); + } + if (ctx->GetOption(RSK_SHUFFLE_OI)) { + AddItemToMainPool(RG_ABILITY_OI); + AddPlentifulCopy(RG_ABILITY_OI); + ctx->possibleIceTrapModels.push_back(RG_ABILITY_OI); + } + if (ctx->GetOption(RSK_SHUFFLE_QPA)) { + AddItemToMainPool(RG_ABILITY_QPA); + AddPlentifulCopy(RG_ABILITY_QPA); + ctx->possibleIceTrapModels.push_back(RG_ABILITY_QPA); + } + if (ctx->GetOption(RSK_SHUFFLE_HESS)) { + AddItemToMainPool(RG_ABILITY_HESS); + AddPlentifulCopy(RG_ABILITY_HESS); + ctx->possibleIceTrapModels.push_back(RG_ABILITY_HESS); + } + if (ctx->GetOption(RSK_SHUFFLE_SUPERSLIDE)) { + AddItemToMainPool(RG_ABILITY_SUPERSLIDE); + AddPlentifulCopy(RG_ABILITY_SUPERSLIDE); + ctx->possibleIceTrapModels.push_back(RG_ABILITY_SUPERSLIDE); + } + if (ctx->GetOption(RSK_SHUFFLE_HOVER)) { + AddItemToMainPool(RG_ABILITY_HOVER); + AddPlentifulCopy(RG_ABILITY_HOVER); + ctx->possibleIceTrapModels.push_back(RG_ABILITY_HOVER); + } + if (ctx->GetOption(RSK_SHUFFLE_EQUIP_SWAP)) { + AddItemToMainPool(RG_ABILITY_EQUIP_SWAP); + AddPlentifulCopy(RG_ABILITY_EQUIP_SWAP); + ctx->possibleIceTrapModels.push_back(RG_ABILITY_EQUIP_SWAP); + } + if (ctx->GetOption(RSK_SHUFFLE_GROUND_JUMP)) { + AddItemToMainPool(RG_ABILITY_GROUND_JUMP); + AddPlentifulCopy(RG_ABILITY_GROUND_JUMP); + ctx->possibleIceTrapModels.push_back(RG_ABILITY_GROUND_JUMP); + } + if (ctx->GetOption(RSK_SHUFFLE_WEIRDSHOT)) { + AddItemToMainPool(RG_ABILITY_WEIRDSHOT); + AddPlentifulCopy(RG_ABILITY_WEIRDSHOT); + ctx->possibleIceTrapModels.push_back(RG_ABILITY_WEIRDSHOT); + } + if (ctx->GetOption(RSK_SHUFFLE_BEEHIVES)) { // 32 total beehive locations AddItemToPool(PendingJunkPool, RG_RED_RUPEE, 23); @@ -697,16 +762,35 @@ void GenerateItemPool() { if (ctx->GetOption(RSK_SHUFFLE_ADULT_TRADE)) { AddItemToMainPool(RG_POCKET_EGG); + AddPlentifulCopy(RG_POCKET_EGG); AddItemToMainPool(RG_COJIRO); + AddPlentifulCopy(RG_COJIRO); AddItemToMainPool(RG_ODD_MUSHROOM); + AddPlentifulCopy(RG_ODD_MUSHROOM); AddItemToMainPool(RG_ODD_POTION); + AddPlentifulCopy(RG_ODD_POTION); AddItemToMainPool(RG_POACHERS_SAW); + AddPlentifulCopy(RG_POACHERS_SAW); AddItemToMainPool(RG_BROKEN_SWORD); + AddPlentifulCopy(RG_BROKEN_SWORD); AddItemToMainPool(RG_PRESCRIPTION); + AddPlentifulCopy(RG_PRESCRIPTION); AddItemToMainPool(RG_EYEBALL_FROG); + AddPlentifulCopy(RG_EYEBALL_FROG); AddItemToMainPool(RG_EYEDROPS); + AddPlentifulCopy(RG_EYEDROPS); + ctx->possibleIceTrapModels.push_back(RG_POCKET_EGG); + ctx->possibleIceTrapModels.push_back(RG_COJIRO); + ctx->possibleIceTrapModels.push_back(RG_ODD_MUSHROOM); + ctx->possibleIceTrapModels.push_back(RG_ODD_POTION); + ctx->possibleIceTrapModels.push_back(RG_POACHERS_SAW); + ctx->possibleIceTrapModels.push_back(RG_BROKEN_SWORD); + ctx->possibleIceTrapModels.push_back(RG_PRESCRIPTION); + ctx->possibleIceTrapModels.push_back(RG_EYEBALL_FROG); + ctx->possibleIceTrapModels.push_back(RG_EYEDROPS); } AddItemToMainPool(RG_CLAIM_CHECK); + AddPlentifulCopy(RG_CLAIM_CHECK); if (ctx->GetOption(RSK_SHUFFLE_CHEST_MINIGAME).Is(RO_CHEST_GAME_SINGLE_KEYS)) { AddItemToMainPool(RG_TREASURE_GAME_SMALL_KEY, 6); // 6 individual keys @@ -736,6 +820,7 @@ void GenerateItemPool() { } } else { AddItemToMainPool(RG_GOLD_SKULLTULA_TOKEN, 100); + ctx->possibleIceTrapModels.push_back(RG_GOLD_SKULLTULA_TOKEN); } if (ctx->GetOption(RSK_SHUFFLE_100_GS_REWARD)) { @@ -750,13 +835,21 @@ void GenerateItemPool() { if (ctx->GetOption(RSK_SHUFFLE_BOSS_SOULS)) { AddItemToMainPool(RG_GOHMA_SOUL); + AddPlentifulCopy(RG_GOHMA_SOUL); AddItemToMainPool(RG_KING_DODONGO_SOUL); + AddPlentifulCopy(RG_KING_DODONGO_SOUL); AddItemToMainPool(RG_BARINADE_SOUL); + AddPlentifulCopy(RG_BARINADE_SOUL); AddItemToMainPool(RG_PHANTOM_GANON_SOUL); + AddPlentifulCopy(RG_PHANTOM_GANON_SOUL); AddItemToMainPool(RG_VOLVAGIA_SOUL); + AddPlentifulCopy(RG_VOLVAGIA_SOUL); AddItemToMainPool(RG_MORPHA_SOUL); + AddPlentifulCopy(RG_MORPHA_SOUL); AddItemToMainPool(RG_BONGO_BONGO_SOUL); + AddPlentifulCopy(RG_BONGO_BONGO_SOUL); AddItemToMainPool(RG_TWINROVA_SOUL); + AddPlentifulCopy(RG_TWINROVA_SOUL); ctx->possibleIceTrapModels.push_back(RG_GOHMA_SOUL); ctx->possibleIceTrapModels.push_back(RG_KING_DODONGO_SOUL); @@ -766,8 +859,10 @@ void GenerateItemPool() { ctx->possibleIceTrapModels.push_back(RG_MORPHA_SOUL); ctx->possibleIceTrapModels.push_back(RG_BONGO_BONGO_SOUL); ctx->possibleIceTrapModels.push_back(RG_TWINROVA_SOUL); + if (ctx->GetOption(RSK_SHUFFLE_BOSS_SOULS).Is(RO_BOSS_SOULS_ON_PLUS_GANON)) { AddItemToMainPool(RG_GANON_SOUL); + AddPlentifulCopy(RG_GANON_SOUL); ctx->possibleIceTrapModels.push_back(RG_GANON_SOUL); } } @@ -782,14 +877,17 @@ void GenerateItemPool() { if (ctx->GetOption(RSK_SHUFFLE_DEKU_STICK_BAG)) { AddItemToMainPool(RG_PROGRESSIVE_STICK_UPGRADE); + ctx->possibleIceTrapModels.push_back(RG_PROGRESSIVE_STICK_UPGRADE); } if (ctx->GetOption(RSK_SHUFFLE_DEKU_NUT_BAG)) { AddItemToMainPool(RG_PROGRESSIVE_NUT_UPGRADE); + ctx->possibleIceTrapModels.push_back(RG_PROGRESSIVE_NUT_UPGRADE); } if (ctx->GetOption(RSK_BOMBCHU_BAG).Is(RO_BOMBCHU_BAG_SINGLE)) { AddItemToMainPool(RG_PROGRESSIVE_BOMBCHU_BAG, 5); + ctx->possibleIceTrapModels.push_back(RG_PROGRESSIVE_BOMBCHU_BAG); } else if (ctx->GetOption(RSK_BOMBCHU_BAG).Is(RO_BOMBCHU_BAG_PROGRESSIVE)) { AddItemToMainPool(RG_PROGRESSIVE_BOMBCHU_BAG, 3); if (ctx->GetOption(RSK_ITEM_POOL).Is(RO_ITEM_POOL_PLENTIFUL)) { @@ -826,12 +924,14 @@ void GenerateItemPool() { // Only add key ring if 4 Fortress keys necessary if (ctx->GetOption(RSK_KEYRINGS_GERUDO_FORTRESS) && ctx->GetOption(RSK_KEYRINGS)) { AddItemToMainPool(RG_GERUDO_FORTRESS_KEY_RING); + ctx->possibleIceTrapModels.push_back(RG_GERUDO_FORTRESS_KEY_RING); // Add junk to make up for missing keys for (uint8_t i = 0; i < 3; i++) { AddItemToMainPool(GetJunkItem()); } } else { AddItemToMainPool(RG_GERUDO_FORTRESS_SMALL_KEY, 4); + ctx->possibleIceTrapModels.push_back(RG_GERUDO_FORTRESS_SMALL_KEY); } } if (ctx->GetOption(RSK_ITEM_POOL).Is(RO_ITEM_POOL_PLENTIFUL)) { @@ -956,30 +1056,41 @@ void GenerateItemPool() { } if (ctx->GetOption(RSK_LOCK_OVERWORLD_DOORS)) { - AddItemToPool(ItemPool, RG_GUARD_HOUSE_KEY); - AddItemToPool(ItemPool, RG_MARKET_BAZAAR_KEY); - AddItemToPool(ItemPool, RG_MARKET_POTION_SHOP_KEY); - AddItemToPool(ItemPool, RG_MASK_SHOP_KEY); - AddItemToPool(ItemPool, RG_MARKET_SHOOTING_GALLERY_KEY); - AddItemToPool(ItemPool, RG_BOMBCHU_BOWLING_KEY); - AddItemToPool(ItemPool, RG_TREASURE_CHEST_GAME_BUILDING_KEY); - AddItemToPool(ItemPool, RG_BOMBCHU_SHOP_KEY); - AddItemToPool(ItemPool, RG_RICHARDS_HOUSE_KEY); - AddItemToPool(ItemPool, RG_ALLEY_HOUSE_KEY); - AddItemToPool(ItemPool, RG_KAK_BAZAAR_KEY); - AddItemToPool(ItemPool, RG_KAK_POTION_SHOP_KEY); - AddItemToPool(ItemPool, RG_BOSS_HOUSE_KEY); - AddItemToPool(ItemPool, RG_GRANNYS_POTION_SHOP_KEY); - AddItemToPool(ItemPool, RG_SKULLTULA_HOUSE_KEY); - AddItemToPool(ItemPool, RG_IMPAS_HOUSE_KEY); - AddItemToPool(ItemPool, RG_WINDMILL_KEY); - AddItemToPool(ItemPool, RG_KAK_SHOOTING_GALLERY_KEY); - AddItemToPool(ItemPool, RG_DAMPES_HUT_KEY); - AddItemToPool(ItemPool, RG_TALONS_HOUSE_KEY); - AddItemToPool(ItemPool, RG_STABLES_KEY); - AddItemToPool(ItemPool, RG_BACK_TOWER_KEY); - AddItemToPool(ItemPool, RG_HYLIA_LAB_KEY); - AddItemToPool(ItemPool, RG_FISHING_HOLE_KEY); + std::vector overworldKeys = { + RG_GUARD_HOUSE_KEY, + RG_MARKET_BAZAAR_KEY, + RG_MARKET_POTION_SHOP_KEY, + RG_MASK_SHOP_KEY, + RG_MARKET_SHOOTING_GALLERY_KEY, + RG_BOMBCHU_BOWLING_KEY, + RG_TREASURE_CHEST_GAME_BUILDING_KEY, + RG_BOMBCHU_SHOP_KEY, + RG_RICHARDS_HOUSE_KEY, + RG_ALLEY_HOUSE_KEY, + RG_KAK_BAZAAR_KEY, + RG_KAK_POTION_SHOP_KEY, + RG_BOSS_HOUSE_KEY, + RG_GRANNYS_POTION_SHOP_KEY, + RG_SKULLTULA_HOUSE_KEY, + RG_IMPAS_HOUSE_KEY, + RG_WINDMILL_KEY, + RG_KAK_SHOOTING_GALLERY_KEY, + RG_DAMPES_HUT_KEY, + RG_TALONS_HOUSE_KEY, + RG_STABLES_KEY, + RG_BACK_TOWER_KEY, + RG_HYLIA_LAB_KEY, + RG_FISHING_HOLE_KEY, + }; + + for (auto key : overworldKeys) { + AddItemToPool(ItemPool, key); + AddPlentifulCopy(key); + } + + // Pick one at random for Ice Trap model + size_t index = Random(0, overworldKeys.size() - 1); + ctx->possibleIceTrapModels.push_back(overworldKeys[index]); } // Shopsanity @@ -1051,6 +1162,7 @@ void GenerateItemPool() { // Dodongos Cavern AddItemToMainPool(RG_DEKU_STICK_1); AddItemToMainPool(RG_DEKU_SHIELD); + ctx->possibleIceTrapModels.push_back(RG_DEKU_SHIELD); if (ctx->GetDungeon(Rando::DODONGOS_CAVERN)->IsMQ()) { AddItemToMainPool(RG_RECOVERY_HEART); } else { @@ -1157,11 +1269,13 @@ void GenerateItemPool() { (ctx->GetOption(RSK_SHUFFLE_MERCHANTS).Is(RO_SHUFFLE_MERCHANTS_ALL_BUT_BEANS) || ctx->GetOption(RSK_SHUFFLE_MERCHANTS).Is(RO_SHUFFLE_MERCHANTS_ALL))) { AddItemToMainPool(RG_BOTTLE_WITH_BLUE_POTION); + ctx->possibleIceTrapModels.push_back(RG_BOTTLE_WITH_BLUE_POTION); } else { AddRandomBottle(bottles); } } else { AddItemToMainPool(RG_RUTOS_LETTER); + ctx->possibleIceTrapModels.push_back(RG_RUTOS_LETTER); } } @@ -1214,8 +1328,10 @@ void GenerateItemPool() { for (auto dungeon : ctx->GetDungeons()->GetDungeonList()) { if (dungeon->HasKeyRing() && ctx->GetOption(RSK_KEYSANITY).IsNot(RO_DUNGEON_ITEM_LOC_STARTWITH)) { AddItemToMainPool(dungeon->GetKeyRing()); + ctx->possibleIceTrapModels.push_back(dungeon->GetKeyRing()); } else if (dungeon->GetSmallKeyCount() > 0) { AddItemToMainPool(dungeon->GetSmallKey(), dungeon->GetSmallKeyCount()); + ctx->possibleIceTrapModels.push_back(dungeon->GetSmallKey()); } } } @@ -1228,6 +1344,11 @@ void GenerateItemPool() { AddItemToMainPool(RG_WATER_TEMPLE_BOSS_KEY); AddItemToMainPool(RG_SPIRIT_TEMPLE_BOSS_KEY); AddItemToMainPool(RG_SHADOW_TEMPLE_BOSS_KEY); + ctx->possibleIceTrapModels.push_back(RG_FOREST_TEMPLE_BOSS_KEY); + ctx->possibleIceTrapModels.push_back(RG_FIRE_TEMPLE_BOSS_KEY); + ctx->possibleIceTrapModels.push_back(RG_WATER_TEMPLE_BOSS_KEY); + ctx->possibleIceTrapModels.push_back(RG_SPIRIT_TEMPLE_BOSS_KEY); + ctx->possibleIceTrapModels.push_back(RG_SHADOW_TEMPLE_BOSS_KEY); } if (!ctx->GetOption(RSK_TRIFORCE_HUNT)) { // Don't add GBK to the pool at all for Triforce Hunt. @@ -1239,6 +1360,7 @@ void GenerateItemPool() { ctx->PlaceItemInLocation(RC_GANONS_TOWER_BOSS_KEY_CHEST, RG_GANONS_CASTLE_BOSS_KEY); } else { AddItemToMainPool(RG_GANONS_CASTLE_BOSS_KEY); + ctx->possibleIceTrapModels.push_back(RG_GANONS_CASTLE_BOSS_KEY); } } @@ -1261,6 +1383,7 @@ void GenerateItemPool() { AddItemToMainPool(RG_PROGRESSIVE_GORONSWORD, 2); ctx->possibleIceTrapModels.push_back(RG_PROGRESSIVE_GORONSWORD); } else { + AddPlentifulCopy(RG_BIGGORON_SWORD); ctx->possibleIceTrapModels.push_back(RG_BIGGORON_SWORD); } diff --git a/soh/soh/Enhancements/randomizer/3drando/shops.cpp b/soh/soh/Enhancements/randomizer/3drando/shops.cpp index 06fe5790f83..e69dfbea5b2 100644 --- a/soh/soh/Enhancements/randomizer/3drando/shops.cpp +++ b/soh/soh/Enhancements/randomizer/3drando/shops.cpp @@ -27,7 +27,7 @@ PriceSettingsStruct::PriceSettingsStruct(RandomizerSettingKey _main, RandomizerS affordable = _affordable; } -static std::array, 0xF1> trickNameTable; // Table of trick names for ice traps +static std::array, RG_MAX> trickNameTable; // Table of trick names for ice traps bool initTrickNames = false; // Indicates if trick ice trap names have been initialized yet // Set vanilla shop item locations before potentially shuffling @@ -964,6 +964,389 @@ void InitTrickNames() { Text{ "Ocarina C Wright Button", "Touche C'est Droite de l'Ocarina", "C-Rechts-Trigger der Okarina" }, Text{ "Overworld C Right Button", "Trou Droit de l'Ocarina", "C-Rechts-Taste der E-Gitarre" }, }; + trickNameTable[RG_GREG_RUPEE] = { + // TODO_TRANSALTE + Text{ "Morshu the Green Ruby", "Morshu the Green Ruby", "Morshu the Green Ruby" }, + Text{ "Geoffrey the Gray Rupoor", "Geoffrey the Gray Rupoor", "Geoffrey the Gray Rupoor" }, + Text{ "Validation Rupee", "Validation Rupee", "Validation Rupee" }, + Text{ "Gary, just Gary", "Gary, just Gary", "Gary, just Gary" }, + Text{ "Ike the Indigo Ice Trap", "Ike the Indigo Ice Trap", "Ike the Indigo Ice Trap" }, + }; + + trickNameTable[RG_FOREST_TEMPLE_SMALL_KEY] = { + // TODO_TRANSALTE + Text{ "Wind Temple Smol Key", "Wind Temple Smol Key", "Wind Temple Smol Key" }, + Text{ "Woodfall Temple Small Key", "Woodfall Temple Small Key", "Woodfall Temple Small Key" }, + Text{ "Skull Woods Small Key", "Skull Woods Small Key", "Skull Woods Small Key" }, + }; + trickNameTable[RG_FIRE_TEMPLE_SMALL_KEY] = { + // TODO_TRANSALTE + Text{ "Ice Cavern Small Keese", "Ice Cavern Small Keese", "Ice Cavern Small Keese" }, + Text{ "Goron Temple Small Key", "Goron Temple Small Key", "Goron Temple Small Key" }, + Text{ "Eldin Temple Salmon Koi", "Eldin Temple Salmon Koi", "Eldin Temple Salmon Koi" }, + }; + trickNameTable[RG_WATER_TEMPLE_SMALL_KEY] = { + // TODO_TRANSALTE + Text{ "Swamp Palace Small Keese", "Swamp Palace Small Keese", "Swamp Palace Small Keese" }, + Text{ "Great Bay Temple Small Key", "Great Bay Temple Small Key", "Great Bay Temple Small Key" }, + Text{ "Lakebed Temple Small Key", "Lakebed Temple Small Key", "Lakebed Temple Small Key" }, + }; + trickNameTable[RG_SPIRIT_TEMPLE_SMALL_KEY] = { + // TODO_TRANSALTE + Text{ "Light Temple Small Key", "Light Temple Small Key", "Light Temple Small Key" }, + Text{ "Lightning Temple Smol Key", "Lightning Temple Smol Key", "Lightning Temple Smol Key" }, + Text{ "Desert Palace Small Key", "Desert Palace Small Key", "Desert Palace Small Key" }, + Text{ "Stone Tower Small Keese", "Stone Tower Small Keese", "Stone Tower Small Keese" }, + }; + trickNameTable[RG_SHADOW_TEMPLE_SMALL_KEY] = { + // TODO_TRANSALTE + Text{ "Palace of Darkness Small Key", "Palace of Darkness Small Key", "Palace of Darkness Small Key" }, + Text{ "Shrine of Illusion Salmon Koi", "Shrine of Illusion Salmon Koi", "Shrine of Illusion Salmon Koi" }, + Text{ "Palace of Twilight Small Key", "Palace of Twilight Small Key", "Palace of Twilight Small Key" }, + }; + trickNameTable[RG_BOTTOM_OF_THE_WELL_SMALL_KEY] = { + // TODO_TRANSALTE + Text{ "Top of the Wall Small Key", "Top of the Wall Small Key", "Top of the Wall Small Key" }, + Text{ "Breath of the Wild Small Key", "Breath of the Wild Small Key", "Breath of the Wild Small Key" }, + Text{ "Beneath the Well Small Key", "Beneath the Well Small Key", "Beneath the Well Small Key" }, + }; + trickNameTable[RG_GERUDO_TRAINING_GROUND_SMALL_KEY] = { + // TODO_TRANSALTE + Text{ "Gerudo Sanctum Small Key", "Gerudo Sanctum Small Key", "Gerudo Sanctum Small Key" }, + Text{ "Lady's Lair Small Keese", "Lady's Lair Small Keese", "Lady's Lair Small Keese" }, + Text{ "Knight Acadamy Small Key", "Knight Acadamy Small Key", "Knight Acadamy Small Key" }, + }; + trickNameTable[RG_GERUDO_FORTRESS_SMALL_KEY] = { + // TODO_TRANSALTE + Text{ "Fortress of Winds Small Key", "Fortress of Winds Small Key", "Fortress of Winds Small Key" }, + Text{ "Thieve's Town Small Key", "Thieve's Town Small Key", "Thieve's Town Small Key" }, + Text{ "Fortress Centrum Small Key", "Fortress Centrum Small Key", "Fortress Centrum Small Key" }, + Text{ "Forsaken Fortress Smol Key", "Forsaken Fortress Smol Key", "Forsaken Fortress Smol Key" }, + Text{ "Pirate's Fortress Small Key", "Pirate's Fortress Small Key", "Pirate's Fortress Small Key" }, + }; + trickNameTable[RG_GANONS_CASTLE_SMALL_KEY] = { + // TODO_TRANSALTE + Text{ "Hyrule Castle Salmon Koi", "Hyrule Castle Salmon Koi", "Hyrule Castle Salmon Koi" }, + Text{ "Onox's Castle Small Key", "Onox's Castle Small Key", "Onox's Castle Small Key" }, + Text{ "Vaati's Palace Small Key", "Vaati's Palace Small Key", "Vaati's Palace Small Key" }, + }; + + trickNameTable[RG_FOREST_TEMPLE_KEY_RING] = { + // TODO_TRANSALTE + Text{ "Wind Temple Key Ring", "Wind Temple Key Ring", "Wind Temple Key Ring" }, + Text{ "Woodfall Temple Key Ring", "Woodfall Temple Key Ring", "Woodfall Temple Key Ring" }, + Text{ "Skull Woods Key Ring", "Skull Woods Key Ring", "Skull Woods Key Ring" }, + }; + trickNameTable[RG_FIRE_TEMPLE_KEY_RING] = { + // TODO_TRANSALTE + Text{ "Ice Cavern Keese Ring", "Ice Cavern Keese Ring", "Ice Cavern Keese Ring" }, + Text{ "Goron Temple Key Ring", "Goron Temple Key Ring", "Goron Temple Key Ring" }, + Text{ "Eldin Temple Koi Ray", "Eldin Temple Koi Ray", "Eldin Temple Koi Ray" }, + }; + trickNameTable[RG_WATER_TEMPLE_KEY_RING] = { + // TODO_TRANSALTE + Text{ "Swamp Palace Keese Ring", "Swamp Palace Keese Ring", "Swamp Palace Keese Ring" }, + Text{ "Great Bay Temple Key Ring", "Great Bay Temple Key Ring", "Great Bay Temple Key Ring" }, + Text{ "Lakebed Temple Key Ring", "Lakebed Temple Key Ring", "Lakebed Temple Key Ring" }, + }; + trickNameTable[RG_SPIRIT_TEMPLE_KEY_RING] = { + // TODO_TRANSALTE + Text{ "Light Temple Key Ring", "Light Temple Key Ring", "Light Temple Key Ring" }, + Text{ "Lightning Temple Key Ring", "Lightning Temple Key Ring", "Lightning Temple Key Ring" }, + Text{ "Desert Palace Key Ring", "Desert Palace Key Ring", "Desert Palace Key Ring" }, + Text{ "Stone Tower Keese Ring", "Stone Tower Keese Ring", "Stone Tower Keese Ring" }, + }; + trickNameTable[RG_SHADOW_TEMPLE_KEY_RING] = { + // TODO_TRANSALTE + Text{ "Palace of Darkness Key Ring", "Palace of Darkness Key Ring", "Palace of Darkness Key Ring" }, + Text{ "Shrine of Illusion Koi Ray", "Shrine of Illusion Koi Ray", "Shrine of Illusion Koi Ray" }, + Text{ "Palace of Twilight Key Ring", "Palace of Twilight Key Ring", "Palace of Twilight Key Ring" }, + }; + trickNameTable[RG_BOTTOM_OF_THE_WELL_KEY_RING] = { + // TODO_TRANSALTE + Text{ "Top of the Wall Key Ring", "Top of the Wall Key Ring", "Top of the Wall Key Ring" }, + Text{ "Breath of the Wild Key Ring", "Breath of the Wild Key Ring", "Breath of the Wild Key Ring" }, + Text{ "Beneath the Well Key Ring", "Beneath the Well Key Ring", "Beneath the Well Key Ring" }, + }; + trickNameTable[RG_GERUDO_TRAINING_GROUND_KEY_RING] = { + // TODO_TRANSALTE + Text{ "Gerudo Sanctum Key Ring", "Gerudo Sanctum Key Ring", "Gerudo Sanctum Key Ring" }, + Text{ "Lady's Lair Keese Ring", "Lady's Lair Keese Ring", "Lady's Lair Keese Ring" }, + Text{ "Knight Acadamy Key Ring", "Knight Acadamy Key Ring", "Knight Acadamy Key Ring" }, + }; + trickNameTable[RG_GERUDO_FORTRESS_KEY_RING] = { + // TODO_TRANSALTE + Text{ "Fortress of Winds Key Ring", "Fortress of Winds Key Ring", "Fortress of Winds Key Ring" }, + Text{ "Thieve's Town Key Ring", "Thieve's Town Key Ring", "Thieve's Town Key Ring" }, + Text{ "Fortress Centrum Key Ring", "Fortress Centrum Key Ring", "Fortress Centrum Key Ring" }, + Text{ "Forsaken Fortress Key Ring", "Forsaken Fortress Key Ring", "Forsaken Fortresse Key Ring" }, + Text{ "Pirate's Fortress Key Ring", "Pirate's Fortress Key Ring", "Pirate's Fortress Key Ring" }, + }; + trickNameTable[RG_GANONS_CASTLE_KEY_RING] = { + // TODO_TRANSALTE + Text{ "Hyrule Castle Koi Ray", "Hyrule Castle Koi Ray", "Hyrule Castle Koi Ray" }, + Text{ "Onox's Castle Key Ring", "Onox's Castle Key Ring", "Onox's Castle Key Ring" }, + Text{ "Vaati's Palace Key Ring", "Vaati's Palace Key Ring", "Vaati's Palace Key Ring" }, + }; + + trickNameTable[RG_FOREST_TEMPLE_BOSS_KEY] = { + // TODO_TRANSALTE + Text{ "Wind Temple Boss Key", "Wind Temple Boss Key", "Wind Temple Boss Key" }, + Text{ "Woodfall Temple Boss Key", "Woodfall Temple Boss Key", "Woodfall Temple Boss Key" }, + Text{ "Skull Woods Boss Key", "Skull Woods Boss Key", "Skull Woods Boss Key" }, + Text{ "Phantom Ganon's Key", "Phantom Ganon's Key", "Phantom Ganon's Key" }, + Text{ "Deku Tree's Boss Key", "Deku Tree's Boss Key", "Deku Tree's Boss Key" }, + }; + trickNameTable[RG_FIRE_TEMPLE_BOSS_KEY] = { + // TODO_TRANSALTE + Text{ "Ice Cavern Boss Keese", "Ice Cavern Boss Keese", "Ice Cavern Boss Keese" }, + Text{ "Goron Temple Boss Key", "Goron Temple Boss Key", "Goron Temple Boss Key" }, + Text{ "Eldin Temple Boss Koi", "Eldin Temple Boss Koi", "Eldin Temple Boss Koi" }, + Text{ "Volvagia's Key", "Volvagia's Key", "Volvagia's Key" }, + Text{ "Dodongo's Cavern Boss Key", "Dodongo's Cavern Boss Key", "Dodongo's Cavern Boss Key" }, + }; + trickNameTable[RG_WATER_TEMPLE_BOSS_KEY] = { + // TODO_TRANSALTE + Text{ "Swamp Palace Boss Keese", "Swamp Palace Boss Keese", "Swamp Palace Boss Keese" }, + Text{ "Great Bay Temple Boss Key", "Great Bay Temple Boss Key", "Great Bay Temple Boss Key" }, + Text{ "Lakebed Temple Boss Key", "Lakebed Temple Boss Key", "Lakebed Temple Boss Key" }, + Text{ "Morpha's Key", "Morpha's Key", "Morpha's Key" }, + Text{ "Jabu Jabu's Belly Boss Key", "Jabu Jabu's Belly Boss Key", "Jabu Jabu's Belly Boss Key" }, + }; + trickNameTable[RG_SPIRIT_TEMPLE_BOSS_KEY] = { + // TODO_TRANSALTE + Text{ "Light Temple Boss Key", "Light Temple Boss Key", "Light Temple Boss Key" }, + Text{ "Lightning Temple Boss Key", "Lightning Temple Boss Key", "Lightning Temple Boss Key" }, + Text{ "Desert Palace Boss Key", "Desert Palace Boss Key", "Desert Palace Boss Key" }, + Text{ "Stone Tower Boss Keese", "Stone Tower Boss Keese", "Stone Tower Boss Keese" }, + Text{ "Twinrova's Key", "Twinrova's Key", "Twinrova's Key" }, + }; + trickNameTable[RG_SHADOW_TEMPLE_BOSS_KEY] = { + // TODO_TRANSALTE + Text{ "Palace of Darkness Boss Key", "Palace of Darkness Boss Key", "Palace of Darkness Boss Key" }, + Text{ "Shrine of Illusion Bass Koi", "Shrine of Illusion Bass Koi", "Shrine of Illusion Bass Koi" }, + Text{ "Palace of Twilight Boss Key", "Palace of Twilight Boss Key", "Palace of Twilight Boss Key" }, + Text{ "Bongo Bongo's Key", "Bongo Bongo's Key", "Bongo Bongo's Key" }, + }; + trickNameTable[RG_GANONS_CASTLE_BOSS_KEY] = { + // TODO_TRANSALTE + Text{ "Hyrule Castle Bass Koi", "Hyrule Castle Bass Koi", "Hyrule Castle Bass Koi" }, + Text{ "Onox's Castle Boss Key", "Onox's Castle Boss Key", "Onox's Castle Boss Key" }, + Text{ "Vaati's Palace Boss Key", "Vaati's Palace Boss Key", "Vaati's Palace Boss Key" }, + Text{ "Ganondorf's Key", "Ganondorf's Key", "Ganondorf's Key" }, + }; + + trickNameTable[RG_GUARD_HOUSE_KEY] = { + // TODO_TRANSLATE + Text{ "Pot Room Key", "Pot Room Key", "Pot Room Key" }, + Text{ "Poe Shop Keese", "Poe Shop Keese", "Poe Shop Keese" }, + Text{ "Pot Collectors Club Key", "Pot Collectors Club Key", "Pot Collectors Club Key" }, + }; + trickNameTable[RG_MARKET_BAZAAR_KEY] = { + // TODO_TRANSLATE + Text{ "Malo Mart Key", "Malo Mart Key", "Malo Mart Key" }, + Text{ "Zora Shop Key", "Zora Shop Key", "Zora Shop Key" }, + Text{ "Goronu General Store Key", "Goronu General Store Key", "Goronu General Store Key" }, + Text{ "Chudly's Fine Goods Key", "Chudly's Fine Goods Key", "Chudly's Fine Goods Key" }, + }; + trickNameTable[RG_MARKET_POTION_SHOP_KEY] = { + // TODO_TRANSLATE + Text{ "Market Medicine Shop Koi", "Market Medicine Shop Koi", "Market Medicine Shop Koi" }, + Text{ "Market Pharmacy Key", "Market Pharmacy Key", "Market Pharmacy Key" }, + Text{ "Market Drug Store Keese", "Market Drug Store Keese", "Market Drug Store Keese" }, + }; + trickNameTable[RG_MASK_SHOP_KEY] = { + // TODO_TRANSLATE + Text{ "Masked Ship Koi", "Masked Ship Koi", "Masked Ship Koi" }, + Text{ "Madame Couture's Key", "Madame Couture's Key", "Madame Couture's Key" }, + Text{ "South Clock Town Key", "South Clock Town Key", "South Clock Town Key" }, + }; + trickNameTable[RG_MARKET_SHOOTING_GALLERY_KEY] = { + // TODO_TRANSLATE + Text{ "Swamp Shooting Gallery Key", "Swamp Shooting Gallery Key", "Swamp Shooting Gallery Key" }, + Text{ "Koume's Target Shooting Key", "Koume's Target Shooting Key", "Koume's Target Shooting Key" }, + Text{ "Pumpkin Pull Key", "Pumpkin Pull Key", "Pumpkin Pull Key" }, + }; + trickNameTable[RG_BOMBCHU_BOWLING_KEY] = { + // TODO_TRANSLATE + Text{ "Bombchu Gallery Key", "Bombchu Gallery Key", "Bombchu Gallery Key" }, + Text{ "Cucco Bowling Ally Key", "Cucco Bowling Ally Key", "Cucco Bowling Ally Key" }, + Text{ "Snowball Bowling Key", "Snowball Bowling Key", "Snowball Bowling Key" }, + Text{ "Bombsketball Key", "Bombsketball Key", "Bombsketball Key" }, + }; + trickNameTable[RG_TREASURE_CHEST_GAME_BUILDING_KEY] = { + // TODO_TRANSLATE + Text{ "Lucky Treasure Game Koi", "Lucky Treasure Game Koi", "Lucky Treasure Game Koi" }, + Text{ "1 in 32 Key", "1 in 32 Key", "1 in 32 Key" }, + Text{ "Fortune's Coice Key", "Fortune's Coice Key", "Fortune's Coice Key" }, + Text{ "Money Making Game Key", "Money Making Game Key", "Money Making Game Key" }, + Text{ "Trading Card Game Key", "Trading Card Game Key", "Trading Card Game Key" }, + }; + trickNameTable[RG_BOMBCHU_SHOP_KEY] = { + // TODO_TRANSLATE + Text{ "Curiosity Shop Key", "Curiosity Shop Key", "Curiosity Shop Key" }, + Text{ "Barnes Bomb Shop Key", "Barnes Bomb Shop Key", "Barnes Bomb Shop Key" }, + Text{ "Bomb Flower Shop Key", "Bomb Flower Shop Key", "Bomb Flower Shop Key" }, + }; + trickNameTable[RG_RICHARDS_HOUSE_KEY] = { + // TODO_TRANSLATE + Text{ "Stockwell's House Key", "Stockwell's House Key", "Stockwell's House Key" }, + Text{ "Blue Dog's House Key", "Blue Dog's House Key", "Blue Dog's House Key" }, + Text{ "Barkle's House Key", "Barkle's House Key", "Barkle's House Key" }, + Text{ "Chimimi's House Key", "Chimimi's House Key", "Chimimi's House Key" }, + }; + trickNameTable[RG_ALLEY_HOUSE_KEY] = { + // TODO_TRANSLATE + Text{ "Mido's House Key", "Mido's House Key", "Mido's House Key" }, + Text{ "Saria's House Key", "Saria's House Key", "Saria's House Key" }, + Text{ "@'s House Key", "@'s House Key", "@'s House Key" }, + }; + trickNameTable[RG_KAK_BAZAAR_KEY] = { + // TODO_TRANSLATE + Text{ "Skyloft Bazaar Key", "Skyloft Bazaar Key", "Skyloft Bazaar Key" }, + Text{ "Harlequin Bazaar Key", "Harlequin Bazaar Key", "Harlequin Bazaar Key" }, + Text{ "Kokiri Shop Key", "Kokiri Shop Key", "Kokiri Shop Key" }, + Text{ "Goron Shop Key", "Goron Shop Key", "Goron Shop Key" }, + }; + trickNameTable[RG_KAK_POTION_SHOP_KEY] = { + // TODO_TRANSLATE + Text{ "Kak Medicine Shop Keep", "Kak Medicine Shop Keep", "Kak Medicine Shop Keep" }, + Text{ "Kak Pharmacy Key", "Kak Pharmacy Key", "Kak Pharmacy Key" }, + Text{ "Kak Drug Store Keese", "Kak Drug Store Keese", "Kak Drug Store Keese" }, + }; + trickNameTable[RG_BOSS_HOUSE_KEY] = { + // TODO_TRANSLATE + Text{ "Kakariko Village Boss Key", "Kakariko Village Boss Key", "Kakariko Village Boss Key" }, + Text{ "Lord Kohga's House Key", "Lord Kohga's House Key", "Lord Kohga's House Key" }, + Text{ "Twinrova's House Key", "Twinrova's House Key", "Twinrova's House Key" }, + }; + trickNameTable[RG_GRANNYS_POTION_SHOP_KEY] = { + // TODO_TRANSLATE + Text{ "Grandpa's Potion Shop Key", "Grandpa's Potion Shop Key", "Grandpa's Potion Shop Key" }, + Text{ "Witch's Hut Key", "Witch's Hut Key", "Witch's Hut Key" }, + Text{ "Hags's Potion Shop Key", "Hags's Potion Shop Key", "Hags's Potion Shop Key" }, + Text{ "Syrup's Potion Shop Key", "Syrup's Potion Shop Key", "Syrup's Potion Shop Key" }, + }; + trickNameTable[RG_SKULLTULA_HOUSE_KEY] = { + // TODO_TRANSLATE + Text{ "Town Spider House Key", "Town Spider House Key", "Town Spider House Key" }, + Text{ "Jovani's House Key", "Jovani's House Key", "Jovani's House Key" }, + Text{ "Maiamai House Key", "Maiamai House Key", "Maiamai House Key" }, + }; + trickNameTable[RG_IMPAS_HOUSE_KEY] = { + // TODO_TRANSLATE + Text{ "Zelda's House Key", "Zelda's House Key", "Zelda's House Key" }, + Text{ "Sheik's House Key", "Sheik's House Key", "Sheik's House Key" }, + Text{ "Purah's House Key", "Purah's House Key", "Purah's House Key" }, + }; + trickNameTable[RG_WINDMILL_KEY] = { + // TODO_TRANSLATE + Text{ "Wind Switch Key", "Wind Switch Key", "Wind Switch Key" }, + Text{ "Weather Vane Key", "Weather Vane Key", "Weather Vane Key" }, + }; + trickNameTable[RG_KAK_SHOOTING_GALLERY_KEY] = { + // TODO_TRANSLATE + Text{ "Firing Range Key", "Firing Range Key", "Firing Range Key" }, + Text{ "Crossbow Training Key", "Crossbow Training Key", "Crossbow Training Key" }, + Text{ "Goron Target Range Key", "Goron Target Range Key", "Goron Target Range Key" }, + }; + trickNameTable[RG_DAMPES_HUT_KEY] = { + // TODO_TRANSLATE + Text{ "Dampe's Grave Key", "Dampe's Grave Key", "Dampe's Grave Key" }, + Text{ "Dampe Studio Key", "Dampe Studio Key", "Dampe Studio Key" }, + Text{ "Old Man's Cabin Key", "Old Man's Cabin Key", "Old Man's Cabin Key" }, + }; + trickNameTable[RG_TALONS_HOUSE_KEY] = { + // TODO_TRANSLATE + Text{ "Malon's House Koi", "Malon's House Koi", "Malon's House Koi" }, + Text{ "Ingo's House Keese", "Ingo's House Keese", "Ingo's House Keese" }, + Text{ "Mario's House Key", "Mario's House Key", "Mario's House Key" }, + }; + trickNameTable[RG_STABLES_KEY] = { + // TODO_TRANSLATE + Text{ "Corral Key", "Corral Key", "Corral Key" }, + Text{ "Foothill Stable Key", "Foothill Stable Key", "Foothill Stable Key" }, + Text{ "Goat Barn Key", "Goat Barn Key", "Goat Barn Key" }, + }; + trickNameTable[RG_BACK_TOWER_KEY] = { + // TODO_TRANSLATE + Text{ "Tower of Hera Key", "Tower of Hera Key", "Tower of Hera Key" }, + Text{ "Clock Tower Key", "Clock Tower Key", "Clock Tower Key" }, + Text{ "Tingle Tower Key", "Tingle Tower Key", "Tingle Tower Key" }, + Text{ "Skyview Tower Key", "Skyview Tower Key", "Skyview Tower Key" }, + Text{ "Sheikah Tower Key", "Sheikah Tower Key", "Sheikah Tower Key" }, + }; + trickNameTable[RG_HYLIA_LAB_KEY] = { + // TODO_TRANSLATE + Text{ "Marine Research Lab Key", "Marine Research Lab Key", "Marine Research Lab Key" }, + Text{ "Hateno Tech Lab Key", "Hateno Tech Lab Key", "Hateno Tech Lab Key" }, + Text{ "Tough Mango Lab Key", "Tough Mango Lab Key", "Tough Mango Lab Key" }, + }; + trickNameTable[RG_FISHING_HOLE_KEY] = { + // TODO_TRANSLATE + Text{ "Swamp Fishing Hole Key", "Swamp Fishing Hole Key", "Swamp Fishing Hole Key" }, + Text{ "Beaver Race Key", "Beaver Race Key", "Beaver Race Key" }, + Text{ "Squid-Hunt Key", "Squid-Hunt Key", "Squid-Hunt Key" }, + }; + trickNameTable[RG_SKELETON_KEY] = { + Text{ "Master Key", "Master Key", "Master Key" }, + Text{ "Lock Skip", "Lock Skip", "Lock Skip" }, + Text{ "Door Bypass", "Door Bypass", "Door Bypass" }, + }; + + trickNameTable[RG_ABILITY_ISG] = { + Text{ "Sword Lock", "Sword Lock", "Sword Lock" }, + Text{ "Permanent Slash", "Permanent Slash", "Permanent Slash" }, + Text{ "Frozen Swing", "Frozen Swing", "Frozen Swing" }, + }; + + trickNameTable[RG_ABILITY_OI] = { + Text{ "Bottle Ocarina", "Bottle Ocarina", "Bottle Ocarina" }, + Text{ "IO", "IO", "IO" }, + Text{ "Jug", "Jug", "Jug" }, + }; + + trickNameTable[RG_ABILITY_QPA] = { + Text{ "Instant Stow", "Instant Stow", "Instant Stow" }, + Text{ "Quick Sheathe", "Quick Sheathe", "Quick Sheathe" }, + Text{ "Fast Put-Away", "Fast Put-Away", "Fast Put-Away" }, + }; + + trickNameTable[RG_ABILITY_HESS] = { + Text{ "Hyper Slide", "Hyper Slide", "Hyper Slide" }, + Text{ "Momentum Burst", "Momentum Burst", "Momentum Burst" }, + Text{ "Speed Extension", "Speed Extension", "Speed Extension" }, + }; + + trickNameTable[RG_ABILITY_SUPERSLIDE] = { + Text{ "Power Slide", "Power Slide", "Power Slide" }, + Text{ "Extended Slide", "Extended Slide", "Extended Slide" }, + Text{ "Momentum Slide", "Momentum Slide", "Momentum Slide" }, + }; + + trickNameTable[RG_ABILITY_HOVER] = { + Text{ "Air Stall", "Air Stall", "Air Stall" }, + Text{ "Float Step", "Float Step", "Float Step" }, + Text{ "Midair Pause", "Midair Pause", "Midair Pause" }, + }; + + trickNameTable[RG_ABILITY_EQUIP_SWAP] = { + Text{ "Instant Swap", "Instant Swap", "Instant Swap" }, + Text{ "Item Flip", "Item Flip", "Item Flip" }, + Text{ "Quick Swap", "Quick Swap", "Quick Swap" }, + }; + + trickNameTable[RG_ABILITY_GROUND_JUMP] = { + Text{ "Instant Jump", "Instant Jump", "Instant Jump" }, + Text{ "Ground Hops", "Ground Hops", "Ground Hops" }, + Text{ "Phantom Jump", "Phantom Jump", "Phantom Jump" }, + }; + + trickNameTable[RG_ABILITY_WEIRDSHOT] = { + Text{ "Reverse Shot", "Reverse Shot", "Reverse Shot" }, + Text{ "Back Shot", "Back Shot", "Back Shot" }, + Text{ "Recoil Shot", "Recoil Shot", "Recoil Shot" }, + }; /* //Names for individual upgrades, in case progressive names are replaced @@ -1157,14 +1540,23 @@ void InitTrickNames() { }; */ } - -// Generate a fake name for the ice trap based on the item it's displayed as -Text GetIceTrapName(uint8_t id) { - // If the trick names table has not been initialized, do so +Text GetIceTrapName(size_t id) { if (!initTrickNames) { InitTrickNames(); initTrickNames = true; } - // Randomly get the easy, medium, or hard name for the given item id - return RandomElement(trickNameTable[id]); + + // Guard against bad ids + if (id >= trickNameTable.size()) { + return Text{ "Ice Trap" }; + } + + auto& names = trickNameTable[id]; + + // Guard against empty name lists + if (names.empty()) { + return Text{ "Ice Trap" }; + } + + return RandomElement(names); } diff --git a/soh/soh/Enhancements/randomizer/3drando/shops.hpp b/soh/soh/Enhancements/randomizer/3drando/shops.hpp index 81c4a43582b..4491fbb9455 100644 --- a/soh/soh/Enhancements/randomizer/3drando/shops.hpp +++ b/soh/soh/Enhancements/randomizer/3drando/shops.hpp @@ -27,4 +27,4 @@ extern std::vector GetMinVanillaShopItems(int total_replaced); extern uint16_t GetRandomPrice(Rando::Location* loc, PriceSettingsStruct priceSettings); extern uint16_t GetCheapBalancedPrice(); extern int GetShopsanityReplaceAmount(); -extern Text GetIceTrapName(uint8_t id); +extern Text GetIceTrapName(size_t id); diff --git a/soh/soh/Enhancements/randomizer/Plandomizer.cpp b/soh/soh/Enhancements/randomizer/Plandomizer.cpp index 3cea3949f53..bf1cabfcd69 100644 --- a/soh/soh/Enhancements/randomizer/Plandomizer.cpp +++ b/soh/soh/Enhancements/randomizer/Plandomizer.cpp @@ -36,6 +36,15 @@ std::string lastLoadedSpoiler = ""; int32_t temporaryItemIndex = -1; RandomizerCheckArea selectedArea = RCAREA_INVALID; +enum PlandoSearchMode { + PLANDO_SEARCH_ANY = 0, + PLANDO_SEARCH_LOCATION, + PLANDO_SEARCH_ITEM, +}; + +static std::string gPlandoSearchText = ""; +static int gPlandoSearchMode = PLANDO_SEARCH_ANY; + ImVec4 itemColor = ImVec4(1.0f, 1.0f, 1.0f, 1.0f); ImTextureID textureID; ImVec2 imageSize = ImVec2(32.0f, 32.0f); @@ -254,6 +263,19 @@ std::unordered_map itemImageMap = { { RG_SKELETON_KEY, "ITEM_KEY_SMALL" } }; +static std::string ToLowerCopy(std::string s) { + std::transform(s.begin(), s.end(), s.begin(), [](unsigned char c) { return (unsigned char)std::tolower(c); }); + return s; +} + +static bool IContains(const std::string& haystack, const std::string& needle) { + if (needle.empty()) + return true; + auto h = ToLowerCopy(haystack); + auto n = ToLowerCopy(needle); + return h.find(n) != std::string::npos; +} + Rando::Item plandomizerRandoRetrieveItem(RandomizerGet randoGetItem) { auto randoGetItemEntry = Rando::StaticData::RetrieveItem(randoGetItem); return randoGetItemEntry; @@ -356,17 +378,67 @@ std::string extractNumberInParentheses(const std::string& text) { return ""; } +static std::string FormatFileTime(const std::filesystem::file_time_type& ft) { + using namespace std::chrono; + + const auto now = system_clock::now(); + const auto sctp = time_point_cast(ft - std::filesystem::file_time_type::clock::now() + now); + + std::time_t fileTime = system_clock::to_time_t(sctp); + std::time_t nowTime = system_clock::to_time_t(now); + + std::tm fileTm{}; + std::tm nowTm{}; + +#ifdef _WIN32 + localtime_s(&fileTm, &fileTime); + localtime_s(&nowTm, &nowTime); +#else + localtime_r(&fileTime, &fileTm); + localtime_r(&nowTime, &nowTm); +#endif + + std::ostringstream oss; + + if (fileTm.tm_year == nowTm.tm_year && fileTm.tm_yday == nowTm.tm_yday) { + oss << "Today at " << std::put_time(&fileTm, "%I:%M %p"); + } else if (fileTm.tm_year == nowTm.tm_year && fileTm.tm_yday == nowTm.tm_yday - 1) { + oss << "Yesterday at " << std::put_time(&fileTm, "%I:%M %p"); + } else { + oss << std::put_time(&fileTm, "%b %d, %Y %I:%M %p"); + } + + return oss.str(); +} + +static std::unordered_map gSpoilerLogTimeStrings; + void PlandomizerPopulateSeedList() { existingSeedList.clear(); + gSpoilerLogTimeStrings.clear(); + auto spoilerPath = Ship::Context::GetPathRelativeToAppDirectory("Randomizer"); + if (!std::filesystem::exists(spoilerPath)) { + return; + } - if (std::filesystem::exists(spoilerPath)) { - for (const auto& entry : std::filesystem::directory_iterator(spoilerPath)) { - if (entry.is_regular_file() && entry.path().extension() == ".json") { - existingSeedList.push_back(entry.path().stem().string()); - } + std::vector> files; + + for (const auto& entry : std::filesystem::directory_iterator(spoilerPath)) { + if (entry.is_regular_file() && entry.path().extension() == ".json") { + const std::string name = entry.path().stem().string(); + const auto time = entry.last_write_time(); + files.emplace_back(name, time); } } + + // Newest -> oldest + std::sort(files.begin(), files.end(), [](const auto& a, const auto& b) { return a.second > b.second; }); + + for (const auto& f : files) { + existingSeedList.push_back(f.first); + gSpoilerLogTimeStrings[f.first] = FormatFileTime(f.second); + } } void PlandomizerItemImageCorrection(Rando::Item randoItem) { @@ -941,6 +1013,13 @@ void PlandomizerDrawOptions() { UIWidgets::Combobox( "##JsonFiles", &selectedList, existingSeedList, UIWidgets::ComboboxOptions().Color(THEME_COLOR).LabelPosition(UIWidgets::LabelPositions::None)); + if (ImGui::IsItemHovered() && selectedList < existingSeedList.size()) { + const std::string& name = existingSeedList[selectedList]; + auto it = gSpoilerLogTimeStrings.find(name); + if (it != gSpoilerLogTimeStrings.end()) { + UIWidgets::Tooltip(it->second.c_str()); + } + } } else { ImGui::Text("No Spoiler Logs found."); } @@ -1034,7 +1113,7 @@ void PlandomizerDrawOptions() { } if (getTabID == TAB_LOCATIONS) { if (plandoLogData.size() > 0) { - UIWidgets::Combobox("Filter by Area:##AreaFilter", &selectedArea, rcAreaNameMap, + UIWidgets::Combobox("Filter by Area:", &selectedArea, rcAreaNameMap, UIWidgets::ComboboxOptions() .Color(THEME_COLOR) .LabelPosition(UIWidgets::LabelPositions::Near) @@ -1046,6 +1125,25 @@ void PlandomizerDrawOptions() { .Padding(ImVec2(10.f, 6.f)))) { PlandomizerRemoveAllItems(); } + ImGui::SameLine(); + static const std::vector searchModes = { "Any", "Location", "Item" }; + + // Mode dropdown + static size_t modeIdx = 0; + modeIdx = (size_t)gPlandoSearchMode; + UIWidgets::Combobox("Search Type:", &modeIdx, searchModes, + UIWidgets::ComboboxOptions() + .Color(THEME_COLOR) + .LabelPosition(UIWidgets::LabelPositions::Near) + .ComponentAlignment(UIWidgets::ComponentAlignments::Right)); + gPlandoSearchMode = (int)modeIdx; + + ImGui::SameLine(); + UIWidgets::InputString("##SearchText", &gPlandoSearchText, + UIWidgets::InputOptions() + .Color(THEME_COLOR) + .LabelPosition(UIWidgets::LabelPositions::None) + .Tooltip("Search by location or item name")); } } } @@ -1113,7 +1211,33 @@ void PlandomizerDrawLocationsWindow(RandomizerCheckArea rcArea) { for (auto& spoilerData : spoilerLogData) { auto checkID = Rando::StaticData::locationNameToEnum[spoilerData.checkName]; auto randoArea = Rando::StaticData::GetLocation(checkID)->GetArea(); - if (rcArea == RCAREA_INVALID || rcArea == randoArea) { + const bool areaPass = (rcArea == RCAREA_INVALID || rcArea == randoArea); + + const std::string& locationName = spoilerData.checkName; + + // Use ONE item name source (plando reflects current state) + const std::string itemName = plandoLogData[index].checkRewardItem.GetName().english; + + bool searchPass = true; + if (!gPlandoSearchText.empty()) { + switch (gPlandoSearchMode) { + default: + case PLANDO_SEARCH_ANY: + searchPass = + IContains(locationName, gPlandoSearchText) || IContains(itemName, gPlandoSearchText); + break; + + case PLANDO_SEARCH_LOCATION: + searchPass = IContains(locationName, gPlandoSearchText); + break; + + case PLANDO_SEARCH_ITEM: + searchPass = IContains(itemName, gPlandoSearchText); + break; + } + } + + if (areaPass && searchPass) { ImGui::TableNextColumn(); ImGui::TextWrapped("%s", spoilerData.checkName.c_str()); ImGui::TableNextColumn(); diff --git a/soh/soh/Enhancements/randomizer/context.cpp b/soh/soh/Enhancements/randomizer/context.cpp index f821c8ff74f..80d8db5d74b 100644 --- a/soh/soh/Enhancements/randomizer/context.cpp +++ b/soh/soh/Enhancements/randomizer/context.cpp @@ -13,6 +13,8 @@ #include "3drando/hints.hpp" #include "soh/util.h" #include "../kaleido.h" +#include "soh/Network/Archipelago/Archipelago.h" +#include "soh/Network/Archipelago/ArchipelagoConsoleWindow.h" #include #include @@ -359,6 +361,33 @@ void Context::SetSpoilerLoaded(const bool spoilerLoaded) { mSpoilerLoaded = spoilerLoaded; } +void Context::AddReceivedArchipelagoItem(const RandomizerGet item) { + mAPreceiveQueue.emplace(item); +} + +GetItemEntry Context::GetArchipelagoGIEntry() { + if (mAPreceiveQueue.empty()) { + // Something must have gone wrong here, just give a rupee + return ItemTableManager::Instance->RetrieveItemEntry(MOD_NONE, GI_HEART); + } + + // Get the first item from the archipelago queue + RandomizerGet itemId = mAPreceiveQueue.front(); + assert(itemId != RG_NONE); + + Item& item = StaticData::RetrieveItem(itemId); + GetItemEntry itemEntry = item.GetGIEntry_Copy(); + mAPreceiveQueue.pop(); + return itemEntry; +} + +RandomizerGet Context::GetLooksLikeForCheck(RandomizerCheck rc) const { + if (overrides.contains(rc)) { + return overrides.at(rc).LooksLike(); + } + return RG_NONE; +} + GetItemEntry Context::GetFinalGIEntry(const RandomizerCheck rc, const bool checkObtainability, const GetItemID ogItemId) { const auto itemLoc = GetItemLocation(rc); @@ -409,6 +438,24 @@ void Context::ParseSpoiler(const char* spoilerFileName) { } catch (...) { LUSLOG_ERROR("Failed to load Spoiler File: %s", spoilerFileName); } } +void Context::ParseArchipelago() { + mSeedGenerated = false; + mSpoilerLoaded = false; + + Rando::Settings::GetInstance()->ResetExcludedLocations(); + ArchipelagoClient& apClient = ArchipelagoClient::GetInstance(); + ParseArchipelagoItemsLocations(apClient.GetScoutedItems()); + ParseArchipelagoOptions(); + ParseArchipelagoTricks(); + ParseArchipelagoExcludedLocations(); + CreateStaticHints(); + if (!CVarGetInteger(CVAR_REMOTE_ARCHIPELAGO("ShuffleEntrances"), 0)) { + mEntranceShuffler->UnshuffleAllEntrances(); + mDungeons->ResetAllDungeons(); + } + mTrials->RemoveAllTrials(); +} + void Context::ParseHashIconIndexesJson(nlohmann::json spoilerFileJson) { nlohmann::json hashJson = spoilerFileJson["file_hash"]; int index = 0; @@ -419,6 +466,7 @@ void Context::ParseHashIconIndexesJson(nlohmann::json spoilerFileJson) { } void Context::ParseItemLocationsJson(nlohmann::json spoilerFileJson) { + // first fill all the items with their vanilla location nlohmann::json locationsJson = spoilerFileJson["locations"]; for (auto it = locationsJson.begin(); it != locationsJson.end(); ++it) { RandomizerCheck rc = StaticData::locationNameToEnum[it.key()]; @@ -441,6 +489,375 @@ void Context::ParseItemLocationsJson(nlohmann::json spoilerFileJson) { } } +void Context::ParseArchipelagoOptions() { + // Set options to what Archipelago expects. Need to slowly convert these to options in apworld and + // load those in instead. + + nlohmann::json slotData = ArchipelagoClient::GetInstance().GetSlotData(); + mOptions[RSK_LOGIC_RULES].Set(RO_LOGIC_GLITCHLESS); + mOptions[RSK_FOREST].Set(slotData["closed_forest"]); + mOptions[RSK_KAK_GATE].Set(slotData["kakariko_gate"]); + mOptions[RSK_DOOR_OF_TIME].Set(slotData["door_of_time"]); + mOptions[RSK_ZORAS_FOUNTAIN].Set(slotData["zoras_fountain"]); + mOptions[RSK_SLEEPING_WATERFALL].Set(slotData["sleeping_waterfall"]); + mOptions[RSK_JABU_OPEN].Set(slotData["jabu_jabu"]); + mOptions[RSK_STARTING_AGE].Set(slotData["starting_age"]); + mOptions[RSK_SELECTED_STARTING_AGE].Set(slotData["starting_age"]); + mOptions[RSK_GERUDO_FORTRESS].Set(slotData["fortress_carpenters"]); + mOptions[RSK_RAINBOW_BRIDGE].Set(slotData["rainbow_bridge"]); + mOptions[RSK_RAINBOW_BRIDGE_STONE_COUNT].Set(slotData["rainbow_bridge_stones_required"]); + mOptions[RSK_RAINBOW_BRIDGE_MEDALLION_COUNT].Set(slotData["rainbow_bridge_medallions_required"]); + mOptions[RSK_RAINBOW_BRIDGE_REWARD_COUNT].Set(slotData["rainbow_bridge_dungeon_rewards_required"]); + mOptions[RSK_RAINBOW_BRIDGE_DUNGEON_COUNT].Set(slotData["rainbow_bridge_dungeons_required"]); + mOptions[RSK_RAINBOW_BRIDGE_TOKEN_COUNT].Set(slotData["rainbow_bridge_skull_tokens_required"]); + mOptions[RSK_BRIDGE_OPTIONS].Set(slotData["rainbow_bridge_greg_modifier"]); + if (slotData["skip_ganons_trials"] == 0) { + mOptions[RSK_GANONS_TRIALS].Set(RO_GANONS_TRIALS_SET_NUMBER); + } else { + mOptions[RSK_GANONS_TRIALS].Set(RO_GANONS_TRIALS_SKIP); + } + mOptions[RSK_TRIAL_COUNT].Set(6); + mOptions[RSK_STARTING_OCARINA].Set(RO_GENERIC_NO); + mOptions[RSK_SHUFFLE_OCARINA].Set(RO_GENERIC_YES); + mOptions[RSK_SHUFFLE_OCARINA_BUTTONS].Set(slotData["shuffle_ocarina_buttons"]); + mOptions[RSK_SHUFFLE_SWIM].Set(slotData["shuffle_swim"]); + mOptions[RSK_STARTING_DEKU_SHIELD].Set(RO_GENERIC_NO); + mOptions[RSK_STARTING_KOKIRI_SWORD].Set(RO_GENERIC_NO); + mOptions[RSK_STARTING_MASTER_SWORD].Set(RO_GENERIC_NO); + mOptions[RSK_STARTING_ZELDAS_LULLABY].Set(RO_GENERIC_NO); + mOptions[RSK_STARTING_EPONAS_SONG].Set(RO_GENERIC_NO); + mOptions[RSK_STARTING_SARIAS_SONG].Set(RO_GENERIC_NO); + mOptions[RSK_STARTING_SUNS_SONG].Set(RO_GENERIC_NO); + mOptions[RSK_STARTING_SONG_OF_TIME].Set(RO_GENERIC_NO); + mOptions[RSK_STARTING_SONG_OF_STORMS].Set(RO_GENERIC_NO); + mOptions[RSK_STARTING_MINUET_OF_FOREST].Set(RO_GENERIC_NO); + mOptions[RSK_STARTING_BOLERO_OF_FIRE].Set(RO_GENERIC_NO); + mOptions[RSK_STARTING_SERENADE_OF_WATER].Set(RO_GENERIC_NO); + mOptions[RSK_STARTING_REQUIEM_OF_SPIRIT].Set(RO_GENERIC_NO); + mOptions[RSK_STARTING_NOCTURNE_OF_SHADOW].Set(RO_GENERIC_NO); + mOptions[RSK_STARTING_PRELUDE_OF_LIGHT].Set(RO_GENERIC_NO); + mOptions[RSK_SHUFFLE_KOKIRI_SWORD].Set(RO_GENERIC_YES); + mOptions[RSK_SHUFFLE_MASTER_SWORD].Set(slotData["shuffle_master_sword"]); + mOptions[RSK_SHUFFLE_CHILD_WALLET].Set(slotData["shuffle_childs_wallet"]); + mOptions[RSK_INCLUDE_TYCOON_WALLET].Set(RO_GENERIC_YES); + if (slotData["shuffle_dungeon_rewards"] == 0) { + mOptions[RSK_SHUFFLE_DUNGEON_REWARDS].Set(RO_DUNGEON_REWARDS_VANILLA); + } else if (slotData["shuffle_dungeon_rewards"] == 1) { + mOptions[RSK_SHUFFLE_DUNGEON_REWARDS].Set(RO_DUNGEON_REWARDS_END_OF_DUNGEON); + } else if (slotData["shuffle_dungeon_rewards"] == 2) { + mOptions[RSK_SHUFFLE_DUNGEON_REWARDS].Set(RO_DUNGEON_REWARDS_ANYWHERE); + } + mOptions[RSK_SHUFFLE_SONGS].Set(RO_SONG_SHUFFLE_ANYWHERE); + // Shuffle tokens is always set to all so the tokens are properly send to the AP server. + // Instead, over at AP's side, we're placing tokens on vanilla locations properly based + // on the chosen settings. + mOptions[RSK_SHUFFLE_TOKENS].Set(RO_TOKENSANITY_ALL); + mOptions[RSK_SHOPSANITY].Set(slotData["shuffle_shops"]); + mOptions[RSK_SHOPSANITY_COUNT].Set(slotData["shuffle_shops_item_amount"]); + mOptions[RSK_SHOPSANITY_PRICES].Set(RO_PRICE_FIXED); + mOptions[RSK_SHOPSANITY_PRICES_FIXED_PRICE].Set(1); + mOptions[RSK_SHOPSANITY_PRICES_RANGE_1].Set(0); + mOptions[RSK_SHOPSANITY_PRICES_RANGE_2].Set(0); + mOptions[RSK_SHOPSANITY_PRICES_NO_WALLET_WEIGHT].Set(0); + mOptions[RSK_SHOPSANITY_PRICES_CHILD_WALLET_WEIGHT].Set(0); + mOptions[RSK_SHOPSANITY_PRICES_ADULT_WALLET_WEIGHT].Set(0); + mOptions[RSK_SHOPSANITY_PRICES_GIANT_WALLET_WEIGHT].Set(0); + mOptions[RSK_SHOPSANITY_PRICES_TYCOON_WALLET_WEIGHT].Set(0); + mOptions[RSK_SHOPSANITY_PRICES_AFFORDABLE].Set(0); + if (slotData["shuffle_scrubs"] == 1) { + mOptions[RSK_SHUFFLE_SCRUBS].Set(RO_SCRUBS_ALL); + } else { + mOptions[RSK_SHUFFLE_SCRUBS].Set(RO_SCRUBS_OFF); + } + mOptions[RSK_SCRUBS_PRICES].Set(RO_PRICE_FIXED); + mOptions[RSK_SCRUBS_PRICES_FIXED_PRICE].Set(1); + mOptions[RSK_SCRUBS_PRICES_RANGE_1].Set(0); + mOptions[RSK_SCRUBS_PRICES_RANGE_2].Set(0); + mOptions[RSK_SCRUBS_PRICES_NO_WALLET_WEIGHT].Set(0); + mOptions[RSK_SCRUBS_PRICES_CHILD_WALLET_WEIGHT].Set(0); + mOptions[RSK_SCRUBS_PRICES_ADULT_WALLET_WEIGHT].Set(0); + mOptions[RSK_SCRUBS_PRICES_GIANT_WALLET_WEIGHT].Set(0); + mOptions[RSK_SCRUBS_PRICES_TYCOON_WALLET_WEIGHT].Set(0); + mOptions[RSK_SCRUBS_PRICES_AFFORDABLE].Set(0); + mOptions[RSK_SHUFFLE_BEEHIVES].Set(slotData["shuffle_beehives"]); + mOptions[RSK_SHUFFLE_COWS].Set(slotData["shuffle_cows"]); + // Ship shows Malon Egg location if weird egg is shuffled even with "Skip Child Zelda" on + // So manually set this to off. + if (slotData["skip_child_zelda"] == 0) { + mOptions[RSK_SHUFFLE_WEIRD_EGG].Set(slotData["shuffle_weird_egg"]); + } else { + mOptions[RSK_SHUFFLE_WEIRD_EGG].Set(RO_GENERIC_NO); + } + mOptions[RSK_SHUFFLE_GERUDO_MEMBERSHIP_CARD].Set(RO_GENERIC_YES); + mOptions[RSK_SHUFFLE_POTS].Set(slotData["shuffle_pots"]); + mOptions[RSK_SHUFFLE_CRATES].Set(slotData["shuffle_crates"]); + mOptions[RSK_SHUFFLE_FROG_SONG_RUPEES].Set(slotData["shuffle_frog_song_rupees"]); + mOptions[RSK_ITEM_POOL].Set(0); + mOptions[RSK_ICE_TRAPS].Set(0); + mOptions[RSK_GOSSIP_STONE_HINTS].Set(RO_GOSSIP_STONES_NONE); + mOptions[RSK_TOT_ALTAR_HINT].Set(RO_GENERIC_NO); + mOptions[RSK_GANONDORF_HINT].Set(RO_GENERIC_NO); + mOptions[RSK_SHEIK_LA_HINT].Set(RO_GENERIC_NO); + mOptions[RSK_DAMPES_DIARY_HINT].Set(RO_GENERIC_NO); + mOptions[RSK_GREG_HINT].Set(RO_GENERIC_NO); + mOptions[RSK_LOACH_HINT].Set(RO_GENERIC_NO); + mOptions[RSK_SARIA_HINT].Set(RO_GENERIC_NO); + mOptions[RSK_FROGS_HINT].Set(RO_GENERIC_NO); + mOptions[RSK_OOT_HINT].Set(RO_GENERIC_NO); + mOptions[RSK_KAK_10_SKULLS_HINT].Set(RO_GENERIC_NO); + mOptions[RSK_KAK_20_SKULLS_HINT].Set(RO_GENERIC_NO); + mOptions[RSK_KAK_30_SKULLS_HINT].Set(RO_GENERIC_NO); + mOptions[RSK_KAK_40_SKULLS_HINT].Set(RO_GENERIC_NO); + mOptions[RSK_KAK_50_SKULLS_HINT].Set(RO_GENERIC_NO); + mOptions[RSK_KAK_100_SKULLS_HINT].Set(RO_GENERIC_NO); + mOptions[RSK_MASK_SHOP_HINT].Set(RO_GENERIC_NO); + mOptions[RSK_BIGGORON_HINT].Set(RO_GENERIC_NO); + mOptions[RSK_BIG_POES_HINT].Set(RO_GENERIC_NO); + mOptions[RSK_CHICKENS_HINT].Set(RO_GENERIC_NO); + mOptions[RSK_MALON_HINT].Set(RO_GENERIC_NO); + mOptions[RSK_HBA_HINT].Set(RO_GENERIC_NO); + mOptions[RSK_WARP_SONG_HINTS].Set(RO_GENERIC_NO); + mOptions[RSK_SCRUB_TEXT_HINT].Set(RO_GENERIC_NO); + mOptions[RSK_MERCHANT_TEXT_HINT].Set(RO_GENERIC_NO); + mOptions[RSK_FISHING_POLE_HINT].Set(RO_GENERIC_NO); + mOptions[RSK_HINT_CLARITY].Set(0); + mOptions[RSK_HINT_DISTRIBUTION].Set(0); + if (slotData["maps_and_compasses"] == 0) { + mOptions[RSK_SHUFFLE_MAPANDCOMPASS].Set(RO_DUNGEON_ITEM_LOC_STARTWITH); + } else if (slotData["maps_and_compasses"] == 1) { + mOptions[RSK_SHUFFLE_MAPANDCOMPASS].Set(RO_DUNGEON_ITEM_LOC_ANYWHERE); + } + mOptions[RSK_KEYSANITY].Set(RO_DUNGEON_ITEM_LOC_ANYWHERE); + mOptions[RSK_GERUDO_KEYS].Set(RO_GERUDO_KEYS_ANYWHERE); + mOptions[RSK_BOSS_KEYSANITY].Set(RO_DUNGEON_ITEM_LOC_ANYWHERE); + if (slotData["ganons_castle_boss_key"] == 0) { + mOptions[RSK_GANONS_BOSS_KEY].Set(RO_GANON_BOSS_KEY_VANILLA); + } else if (slotData["ganons_castle_boss_key"] == 1) { + mOptions[RSK_GANONS_BOSS_KEY].Set(RO_GANON_BOSS_KEY_ANYWHERE); + } else if (slotData["ganons_castle_boss_key"] == 2) { + mOptions[RSK_GANONS_BOSS_KEY].Set(RO_GANON_BOSS_KEY_LACS_VANILLA); + } else if (slotData["ganons_castle_boss_key"] == 3) { + mOptions[RSK_GANONS_BOSS_KEY].Set(RO_GANON_BOSS_KEY_LACS_STONES); + } else if (slotData["ganons_castle_boss_key"] == 4) { + mOptions[RSK_GANONS_BOSS_KEY].Set(RO_GANON_BOSS_KEY_LACS_MEDALLIONS); + } else if (slotData["ganons_castle_boss_key"] == 5) { + mOptions[RSK_GANONS_BOSS_KEY].Set(RO_GANON_BOSS_KEY_LACS_REWARDS); + } else if (slotData["ganons_castle_boss_key"] == 6) { + mOptions[RSK_GANONS_BOSS_KEY].Set(RO_GANON_BOSS_KEY_LACS_DUNGEONS); + } else if (slotData["ganons_castle_boss_key"] == 7) { + mOptions[RSK_GANONS_BOSS_KEY].Set(RO_GANON_BOSS_KEY_LACS_TOKENS); + } + mOptions[RSK_SKIP_CHILD_ZELDA].Set(slotData["skip_child_zelda"]); + mOptions[RSK_STARTING_STICKS].Set(RO_GENERIC_NO); + mOptions[RSK_STARTING_NUTS].Set(RO_GENERIC_NO); + mOptions[RSK_FULL_WALLETS].Set(slotData["full_wallets"]); + mOptions[RSK_SHUFFLE_CHEST_MINIGAME].Set(RO_GENERIC_NO); + mOptions[RSK_BIG_POE_COUNT].Set(slotData["big_poe_target_count"]); + mOptions[RSK_SKIP_EPONA_RACE].Set(slotData["skip_epona_race"]); + mOptions[RSK_COMPLETE_MASK_QUEST].Set(slotData["complete_mask_quest"]); + mOptions[RSK_SKIP_SCARECROWS_SONG].Set(slotData["skip_scarecrows_song"]); + mOptions[RSK_SKULLS_SUNS_SONG].Set(slotData["skulls_sun_song"]); + mOptions[RSK_SHUFFLE_ADULT_TRADE].Set(slotData["shuffle_adult_trade_items"]); + mOptions[RSK_SHUFFLE_MERCHANTS].Set(slotData["shuffle_merchants"]); + mOptions[RSK_MERCHANT_PRICES].Set(0); + mOptions[RSK_MERCHANT_PRICES_FIXED_PRICE].Set(0); + mOptions[RSK_MERCHANT_PRICES_RANGE_1].Set(0); + mOptions[RSK_MERCHANT_PRICES_RANGE_2].Set(0); + mOptions[RSK_MERCHANT_PRICES_NO_WALLET_WEIGHT].Set(0); + mOptions[RSK_MERCHANT_PRICES_CHILD_WALLET_WEIGHT].Set(0); + mOptions[RSK_MERCHANT_PRICES_ADULT_WALLET_WEIGHT].Set(0); + mOptions[RSK_MERCHANT_PRICES_GIANT_WALLET_WEIGHT].Set(0); + mOptions[RSK_MERCHANT_PRICES_TYCOON_WALLET_WEIGHT].Set(0); + mOptions[RSK_MERCHANT_PRICES_AFFORDABLE].Set(0); + mOptions[RSK_BLUE_FIRE_ARROWS].Set(slotData["blue_fire_arrows"]); + mOptions[RSK_SUNLIGHT_ARROWS].Set(slotData["sunlight_arrows"]); + mOptions[RSK_SLINGBOW_BREAK_BEEHIVES].Set(slotData["slingbow_break_beehives"]); + mOptions[RSK_ENABLE_BOMBCHU_DROPS].Set(slotData["bombchu_drops"]); + mOptions[RSK_BOMBCHU_BAG].Set(2); + mOptions[RSK_LINKS_POCKET].Set(RO_LINKS_POCKET_ANYTHING); + mOptions[RSK_MQ_DUNGEON_RANDOM].Set(0); + mOptions[RSK_MQ_DUNGEON_COUNT].Set(0); + mOptions[RSK_MQ_DUNGEON_SET].Set(0); + mOptions[RSK_MQ_DEKU_TREE].Set(0); + mOptions[RSK_MQ_DODONGOS_CAVERN].Set(0); + mOptions[RSK_MQ_JABU_JABU].Set(0); + mOptions[RSK_MQ_FOREST_TEMPLE].Set(0); + mOptions[RSK_MQ_FIRE_TEMPLE].Set(0); + mOptions[RSK_MQ_WATER_TEMPLE].Set(0); + mOptions[RSK_MQ_SPIRIT_TEMPLE].Set(0); + mOptions[RSK_MQ_SHADOW_TEMPLE].Set(0); + mOptions[RSK_MQ_BOTTOM_OF_THE_WELL].Set(0); + mOptions[RSK_MQ_ICE_CAVERN].Set(0); + mOptions[RSK_MQ_GTG].Set(0); + mOptions[RSK_MQ_GANONS_CASTLE].Set(0); + mOptions[RSK_LACS_STONE_COUNT].Set(slotData["ganons_castle_boss_key_stones_required"]); + mOptions[RSK_LACS_MEDALLION_COUNT].Set(slotData["ganons_castle_boss_key_medallions_required"]); + mOptions[RSK_LACS_REWARD_COUNT].Set(slotData["ganons_castle_boss_key_dungeon_rewards_required"]); + mOptions[RSK_LACS_DUNGEON_COUNT].Set(slotData["ganons_castle_boss_key_dungeons_required"]); + mOptions[RSK_LACS_TOKEN_COUNT].Set(slotData["ganons_castle_boss_key_skull_tokens_required"]); + mOptions[RSK_LACS_OPTIONS].Set(slotData["ganons_castle_boss_key_greg_modifier"]); + if (slotData["key_rings"] == 0) { + mOptions[RSK_KEYRINGS].Set(RO_KEYRINGS_OFF); + } else if (slotData["key_rings"] == 1) { + mOptions[RSK_KEYRINGS].Set(RO_KEYRINGS_COUNT); + } + mOptions[RSK_KEYRINGS_RANDOM_COUNT].Set(9); + mOptions[RSK_KEYRINGS_GERUDO_FORTRESS].Set(0); + mOptions[RSK_KEYRINGS_FOREST_TEMPLE].Set(0); + mOptions[RSK_KEYRINGS_FIRE_TEMPLE].Set(0); + mOptions[RSK_KEYRINGS_WATER_TEMPLE].Set(0); + mOptions[RSK_KEYRINGS_SPIRIT_TEMPLE].Set(0); + mOptions[RSK_KEYRINGS_SHADOW_TEMPLE].Set(0); + mOptions[RSK_KEYRINGS_BOTTOM_OF_THE_WELL].Set(0); + mOptions[RSK_KEYRINGS_GTG].Set(0); + mOptions[RSK_KEYRINGS_GANONS_CASTLE].Set(0); + if (!CVarGetInteger(CVAR_REMOTE_ARCHIPELAGO("ShuffleEntrances"), 0)) { + mOptions[RSK_SHUFFLE_ENTRANCES].Set(0); + mOptions[RSK_SHUFFLE_DUNGEON_ENTRANCES].Set(0); + mOptions[RSK_SHUFFLE_OVERWORLD_ENTRANCES].Set(0); + mOptions[RSK_SHUFFLE_INTERIOR_ENTRANCES].Set(0); + mOptions[RSK_SHUFFLE_THIEVES_HIDEOUT_ENTRANCES].Set(0); + mOptions[RSK_SHUFFLE_GROTTO_ENTRANCES].Set(0); + mOptions[RSK_SHUFFLE_OWL_DROPS].Set(0); + mOptions[RSK_SHUFFLE_WARP_SONGS].Set(0); + mOptions[RSK_SHUFFLE_OVERWORLD_SPAWNS].Set(0); + mOptions[RSK_MIXED_ENTRANCE_POOLS].Set(0); + mOptions[RSK_MIX_DUNGEON_ENTRANCES].Set(0); + mOptions[RSK_MIX_BOSS_ENTRANCES].Set(0); + mOptions[RSK_MIX_OVERWORLD_ENTRANCES].Set(0); + mOptions[RSK_MIX_INTERIOR_ENTRANCES].Set(0); + mOptions[RSK_MIX_THIEVES_HIDEOUT_ENTRANCES].Set(0); + mOptions[RSK_MIX_GROTTO_ENTRANCES].Set(0); + mOptions[RSK_DECOUPLED_ENTRANCES].Set(0); + } + mOptions[RSK_STARTING_SKULLTULA_TOKEN].Set(0); + mOptions[RSK_STARTING_HEARTS].Set(2); + mOptions[RSK_DAMAGE_MULTIPLIER].Set(0); + mOptions[RSK_ALL_LOCATIONS_REACHABLE].Set(0); + mOptions[RSK_SHUFFLE_BOSS_ENTRANCES].Set(0); + mOptions[RSK_SHUFFLE_100_GS_REWARD].Set(slotData["shuffle_100_gs_reward"]); + mOptions[RSK_TRIFORCE_HUNT].Set(slotData["triforce_hunt"]); + // For some reason, ship adds 1 after the option is parsed in normal rando, so we subtract 1 here. + uint8_t triforcePieceTotal = slotData["triforce_hunt_pieces_total"]; + uint8_t triforcePieceRequired = slotData["triforce_hunt_pieces_required"]; + mOptions[RSK_TRIFORCE_HUNT_PIECES_TOTAL].Set((triforcePieceTotal - 1)); + mOptions[RSK_TRIFORCE_HUNT_PIECES_REQUIRED].Set((triforcePieceRequired - 1)); + mOptions[RSK_SHUFFLE_BOSS_SOULS].Set(slotData["shuffle_boss_souls"]); + if (slotData["shuffle_fish"] == 0) { + mOptions[RSK_FISHSANITY].Set(RO_FISHSANITY_OFF); + } else if (slotData["shuffle_fish"] == 1) { + mOptions[RSK_FISHSANITY].Set(RO_FISHSANITY_POND); + } else if (slotData["shuffle_fish"] == 2) { + mOptions[RSK_FISHSANITY].Set(RO_FISHSANITY_OVERWORLD); + } else if (slotData["shuffle_fish"] == 3) { + mOptions[RSK_FISHSANITY].Set(RO_FISHSANITY_BOTH); + } + mOptions[RSK_FISHSANITY_POND_COUNT].Set(15); + mOptions[RSK_FISHSANITY_AGE_SPLIT].Set(1); + mOptions[RSK_SHUFFLE_FISHING_POLE].Set(slotData["shuffle_fishing_pole"]); + mOptions[RSK_INFINITE_UPGRADES].Set(slotData["infinite_upgrades"]); + mOptions[RSK_SKELETON_KEY].Set(slotData["skeleton_key"]); + mOptions[RSK_SHUFFLE_DEKU_STICK_BAG].Set(slotData["shuffle_deku_stick_bag"]); + mOptions[RSK_SHUFFLE_DEKU_NUT_BAG].Set(slotData["shuffle_deku_nut_bag"]); + mOptions[RSK_SHUFFLE_FREESTANDING].Set(slotData["shuffle_freestanding_items"]); + mOptions[RSK_SHUFFLE_FOUNTAIN_FAIRIES].Set(slotData["shuffle_fountain_fairies"]); + mOptions[RSK_SHUFFLE_STONE_FAIRIES].Set(slotData["shuffle_stone_fairies"]); + mOptions[RSK_SHUFFLE_BEAN_FAIRIES].Set(slotData["shuffle_bean_fairies"]); + mOptions[RSK_SHUFFLE_SONG_FAIRIES].Set(slotData["shuffle_song_fairies"]); + mOptions[RSK_LOCK_OVERWORLD_DOORS].Set(slotData["lock_overworld_doors"]); + mOptions[RSK_SHUFFLE_GRASS].Set(slotData["shuffle_grass"]); + mOptions[RSK_SHUFFLE_TREES].Set(slotData["shuffle_trees"]); +} + +void Context::ParseArchipelagoTricks() { + Context::ResetTrickOptions(); + + // TODO: Implement trick parsing from slot data + // See Context::ParseTricksJson for more info +} + +void Context::ParseArchipelagoExcludedLocations() { + // Maybe eventually we can add locations that are excluded on AP's side. + // For now, remove all of them to prevent seed bleed from normal rando seeds. + const auto ctx = Rando::Context::GetInstance(); + for (int count = 0; count < RC_MAX; count++) { + ctx->GetItemLocation(count)->SetExcludedOption(RO_GENERIC_OFF); + }; +} + +void Context::ParseArchipelagoItemsLocations(const std::vector& scouted_items) { + const std::string SlotName = ArchipelagoClient::GetInstance().GetSlotName(); + nlohmann::json slotData = ArchipelagoClient::GetInstance().GetSlotData(); + + // Zero out the location table first + for (int rc = 1; rc < RC_MAX; rc++) { + itemLocationTable[rc].SetPlacedItem(RG_NONE); + } + + for (const ArchipelagoClient::ApItem& ap_item : scouted_items) { + const RandomizerCheck rc = StaticData::locationNameToEnum[ap_item.locationName]; + + if (SlotName == ap_item.playerName) { + // Our item + SPDLOG_TRACE("Populated item {} at location {}", ap_item.itemName, ap_item.locationName); + const RandomizerGet item = StaticData::itemNameToEnum[ap_item.itemName]; + itemLocationTable[rc].SetPlacedItem(item); + + if (item == RG_ICE_TRAP) { + RandomizerGet iceTrapItem = ArchipelagoClient::GetInstance().GetIceTrapItem(); + overrides[rc] = ItemOverride(rc, iceTrapItem); + overrides[rc].SetTrickName(Text(GetIceTrapName(iceTrapItem))); + } + } else { + // Other player item + // If progressive or trap bit flag is set, make item progressive. + if (ap_item.flags & (1 << 0) || ap_item.flags & (1 << 2)) { + itemLocationTable[rc].SetPlacedItem(RG_ARCHIPELAGO_ITEM_PROGRESSIVE); + // If useful bit flag is on, make item useful. + } else if (ap_item.flags & (1 << 1)) { + itemLocationTable[rc].SetPlacedItem(RG_ARCHIPELAGO_ITEM_USEFUL); + // None of these flags being present means it's junk. + } else { + itemLocationTable[rc].SetPlacedItem(RG_ARCHIPELAGO_ITEM_JUNK); + } + } + } + + // Place vanilla shop items + nlohmann::json vanillaShopItems = slotData["shop_vanilla_items"]; + for (auto it = vanillaShopItems.begin(); it != vanillaShopItems.end(); it++) { + std::string location = it.key(); + std::string itemName = it.value(); + const RandomizerCheck rc = StaticData::locationNameToEnum[location]; + const RandomizerGet item = StaticData::itemNameToEnum[itemName]; + itemLocationTable[rc].SetPlacedItem(item); + } + + // Set all shop item prices + nlohmann::json shopPrices = slotData["shop_prices"]; + for (auto it = shopPrices.begin(); it != shopPrices.end(); it++) { + std::string location = it.key(); + uint16_t price = it.value(); + const RandomizerCheck rc = StaticData::locationNameToEnum[location]; + itemLocationTable[rc].SetCustomPrice(price); + } + + // Set all scrub prices + nlohmann::json scrubPrices = slotData["scrub_prices"]; + for (auto it = scrubPrices.begin(); it != scrubPrices.end(); it++) { + std::string location = it.key(); + uint16_t price = it.value(); + const RandomizerCheck rc = StaticData::locationNameToEnum[location]; + itemLocationTable[rc].SetCustomPrice(price); + } + + // Set merchant prices + itemLocationTable[RC_ZR_MAGIC_BEAN_SALESMAN].SetCustomPrice(60); + itemLocationTable[RC_KAK_GRANNYS_SHOP].SetCustomPrice(100); + itemLocationTable[RC_WASTELAND_BOMBCHU_SALESMAN].SetCustomPrice(200); + itemLocationTable[RC_GC_MEDIGORON].SetCustomPrice(200); +} + void Context::WriteHintJson(nlohmann::ordered_json& spoilerFileJson) { for (Hint hint : hintTable) { hint.logHint(spoilerFileJson); diff --git a/soh/soh/Enhancements/randomizer/context.h b/soh/soh/Enhancements/randomizer/context.h index bd20817f511..ca0457d28b7 100644 --- a/soh/soh/Enhancements/randomizer/context.h +++ b/soh/soh/Enhancements/randomizer/context.h @@ -8,6 +8,7 @@ #include "hint.h" #include "fishsanity.h" #include "trial.h" +#include "soh/Network/Archipelago/Archipelago.h" #include #include @@ -68,6 +69,8 @@ class Context { bool IsSpoilerLoaded() const; void SetSpoilerLoaded(bool spoilerLoaded = true); + RandomizerGet GetLooksLikeForCheck(RandomizerCheck rc) const; + /** * @brief Reset all RandomizerTrick keys. */ @@ -116,6 +119,8 @@ class Context { void LACSCondition(RandoOptionLACSCondition lacsCondition); GetItemEntry GetFinalGIEntry(RandomizerCheck rc, bool checkObtainability = true, GetItemID ogItemId = GI_NONE); + void AddReceivedArchipelagoItem(const RandomizerGet item); + GetItemEntry GetArchipelagoGIEntry(); void ParseSpoiler(const char* spoilerFileName); void ParseHashIconIndexesJson(nlohmann::json spoilerFileJson); void ParseItemLocationsJson(nlohmann::json spoilerFileJson); @@ -133,6 +138,12 @@ class Context { bool allLocationsReachable = false; RandomizerArea GetAreaFromString(std::string str); + void ParseArchipelago(); + void ParseArchipelagoOptions(); + void ParseArchipelagoTricks(); + void ParseArchipelagoExcludedLocations(); + void ParseArchipelagoItemsLocations(const std::vector& slot_data); + /** * @brief Get the hash for the current seed. * @@ -195,5 +206,6 @@ class Context { std::string mHash; std::string mSeedString; uint32_t mFinalSeed = 0; + std::queue mAPreceiveQueue = {}; }; } // namespace Rando \ No newline at end of file diff --git a/soh/soh/Enhancements/randomizer/draw.cpp b/soh/soh/Enhancements/randomizer/draw.cpp index 7d266d78bef..11052bad75a 100644 --- a/soh/soh/Enhancements/randomizer/draw.cpp +++ b/soh/soh/Enhancements/randomizer/draw.cpp @@ -183,14 +183,14 @@ extern "C" void Randomizer_DrawCompass(PlayState* play, GetItemEntry* getItemEnt extern "C" void Randomizer_DrawBossKey(PlayState* play, GetItemEntry* getItemEntry) { s8 isCustomKeysEnabled = CVarGetInteger(CVAR_RANDOMIZER_ENHANCEMENT("CustomKeyModels"), 1); - s16 slot = getItemEntry->getItemId - RG_FOREST_TEMPLE_BOSS_KEY; + s16 slot = getItemEntry->drawItemId - RG_FOREST_TEMPLE_BOSS_KEY; - std::string CvarValue[6] = { + static std::string CvarValue[6] = { "gCosmetics.Key.ForestBoss", "gCosmetics.Key.FireBoss", "gCosmetics.Key.WaterBoss", "gCosmetics.Key.SpiritBoss", "gCosmetics.Key.ShadowBoss", "gCosmetics.Key.GanonsBoss", }; - Gfx* CustomdLists[] = { + static Gfx* CustomdLists[] = { (Gfx*)gBossKeyIconForestTempleDL, (Gfx*)gBossKeyIconFireTempleDL, (Gfx*)gBossKeyIconWaterTempleDL, (Gfx*)gBossKeyIconSpiritTempleDL, (Gfx*)gBossKeyIconShadowTempleDL, (Gfx*)gBossKeyIconGanonsCastleDL, }; @@ -1222,3 +1222,29 @@ extern "C" void Randomizer_DrawOverworldKey(PlayState* play, GetItemEntry* getIt CLOSE_DISPS(play->state.gfxCtx); } + +extern "C" void Randomizer_DrawArchipelagoItem(PlayState* play, GetItemEntry* getItemEntry) { + OPEN_DISPS(play->state.gfxCtx); + + Gfx_SetupDL_25Opa(play->state.gfxCtx); + + Matrix_Scale(0.035f, 0.035f, 0.035f, MTXMODE_APPLY); + + // To-do: Implement "Archipelago Item Matches Contents" option. + // Default to "useful" item model. + Gfx* archipelagoItemDL = (Gfx*)gArchipelagoItemDL; + if (true /*ArchipelagoItemMatchesContents==true*/) { + if (getItemEntry->getItemId == RG_ARCHIPELAGO_ITEM_JUNK) { + archipelagoItemDL = (Gfx*)gArchipelagoJunkDL; + } else if (getItemEntry->getItemId == RG_ARCHIPELAGO_ITEM_PROGRESSIVE) { + archipelagoItemDL = (Gfx*)gArchipelagoProgressiveDL; + } + } + + gSPMatrix(POLY_OPA_DISP++, Matrix_NewMtx(play->state.gfxCtx, (char*)__FILE__, __LINE__), + G_MTX_MODELVIEW | G_MTX_LOAD); + + gSPDisplayList(POLY_OPA_DISP++, archipelagoItemDL); + + CLOSE_DISPS(play->state.gfxCtx); +} diff --git a/soh/soh/Enhancements/randomizer/draw.h b/soh/soh/Enhancements/randomizer/draw.h index 8483a7bf966..f81368058ce 100644 --- a/soh/soh/Enhancements/randomizer/draw.h +++ b/soh/soh/Enhancements/randomizer/draw.h @@ -27,6 +27,7 @@ void Randomizer_DrawMysteryItem(PlayState* play, GetItemEntry* getItemEntry); void Randomizer_DrawBombchuBagInLogic(PlayState* play, GetItemEntry* getItemEntry); void Randomizer_DrawBombchuBag(PlayState* play, GetItemEntry* getItemEntry); void Randomizer_DrawOverworldKey(PlayState* play, GetItemEntry* getItemEntry); +void Randomizer_DrawArchipelagoItem(PlayState* play, GetItemEntry* getItemEntry); #define GET_ITEM_MYSTERY \ { \ diff --git a/soh/soh/Enhancements/randomizer/dungeon.cpp b/soh/soh/Enhancements/randomizer/dungeon.cpp index 1eb88ed73a9..928d5699a01 100644 --- a/soh/soh/Enhancements/randomizer/dungeon.cpp +++ b/soh/soh/Enhancements/randomizer/dungeon.cpp @@ -259,4 +259,11 @@ void Dungeons::ParseJson(nlohmann::json spoilerFileJson) { } } } + +void Dungeons::ResetAllDungeons() { + for (auto& dungeon : dungeonList) { + dungeon.ClearMQ(); + } +} + } // namespace Rando diff --git a/soh/soh/Enhancements/randomizer/dungeon.h b/soh/soh/Enhancements/randomizer/dungeon.h index ba81d289d67..830b05a0263 100644 --- a/soh/soh/Enhancements/randomizer/dungeon.h +++ b/soh/soh/Enhancements/randomizer/dungeon.h @@ -90,6 +90,7 @@ class Dungeons { std::array GetDungeonList(); size_t GetDungeonListSize() const; void ParseJson(nlohmann::json spoilerFileJson); + void ResetAllDungeons(); private: std::array dungeonList; diff --git a/soh/soh/Enhancements/randomizer/entrance.cpp b/soh/soh/Enhancements/randomizer/entrance.cpp index 6ae7e993b94..1a85f3d715d 100644 --- a/soh/soh/Enhancements/randomizer/entrance.cpp +++ b/soh/soh/Enhancements/randomizer/entrance.cpp @@ -1708,6 +1708,11 @@ void EntranceShuffler::ApplyEntranceOverrides() { entrance->SetAsShuffled(); } } + +const Entrance* EntranceShuffler::GetEntranceByIndex(int16_t index) { + auto iter = entranceMap.find(index); + return iter != entranceMap.end() ? iter->second : nullptr; +} } // namespace Rando extern "C" EntranceOverride* Randomizer_GetEntranceOverrides() { diff --git a/soh/soh/Enhancements/randomizer/entrance.h b/soh/soh/Enhancements/randomizer/entrance.h index a40ea031b61..edaf75b12eb 100644 --- a/soh/soh/Enhancements/randomizer/entrance.h +++ b/soh/soh/Enhancements/randomizer/entrance.h @@ -132,6 +132,8 @@ class EntranceShuffler { void ParseJson(nlohmann::json spoilerFileJson); void ApplyEntranceOverrides(); + static const Entrance* GetEntranceByIndex(int16_t index); + private: std::vector AssumeEntrancePool(std::vector& entrancePool); bool ShuffleOneWayPriorityEntrances(std::map& oneWayPriorities, diff --git a/soh/soh/Enhancements/randomizer/hook_handlers.cpp b/soh/soh/Enhancements/randomizer/hook_handlers.cpp index fcadbb534d0..6a62f1eea15 100644 --- a/soh/soh/Enhancements/randomizer/hook_handlers.cpp +++ b/soh/soh/Enhancements/randomizer/hook_handlers.cpp @@ -13,6 +13,9 @@ #include "soh/SohGui/ImGuiUtils.h" #include "soh/Notification/Notification.h" #include "soh/SaveManager.h" +#include "soh/Network/Archipelago/ArchipelagoConsoleWindow.h" +#include "soh/frame_interpolation.h" +#include "soh/Enhancements/SkipGIAnimations.h" extern "C" { #include "macros.h" @@ -56,6 +59,7 @@ extern "C" { #include "src/overlays/actors/ovl_Fishing/z_fishing.h" #include "src/overlays/actors/ovl_En_Mk/z_en_mk.h" #include "draw.h" +#include "objects/gameplay_keep/gameplay_keep.h" extern SaveContext gSaveContext; extern PlayState* gPlayState; @@ -70,6 +74,16 @@ extern void EnGe1_Wait_Archery(EnGe1* enGe1, PlayState* play); extern void EnGe1_SetAnimationIdle(EnGe1* enGe1); extern void EnGe1_SetAnimationIdle(EnGe1* enGe1); extern void EnGe2_SetupCapturePlayer(EnGe2* enGe2, PlayState* play); +extern void func_80832318(Player* player); +extern void Player_SetupActionPreserveItemAction(PlayState* play, Player* player, PlayerActionFunc actionFunc, + s32 flags); +extern void Player_Action_Idle(Player* player, PlayState* play); +extern s32 Player_DecelerateToZero(Player* player); +extern s32 func_80834BD4(Player* player, PlayState* play); +} + +static GetItemEntry GetVisualEntry(const GetItemEntry& entry) { + return ItemTableManager::Instance->RetrieveItemEntry((s16)entry.drawModIndex, (GetItemID)entry.drawItemId); } bool LocMatchesQuest(Rando::Location loc) { @@ -219,6 +233,16 @@ bool MeetsRainbowBridgeRequirements() { static std::queue randomizerQueuedChecks; static RandomizerCheck randomizerQueuedCheck = RC_UNKNOWN_CHECK; static GetItemEntry randomizerQueuedItemEntry = GET_ITEM_NONE; +static RandomizerCheck sLastIceTrapCheck = RC_UNKNOWN_CHECK; + +RandomizerCheck GetLastIceTrapCheck() { + return sLastIceTrapCheck; +} + +void ArchipelagoOnReceiveItem(const int32_t item) { + randomizerQueuedChecks.push(RC_ARCHIPELAGO_RECEIVED_ITEM); + Rando::Context::GetInstance()->AddReceivedArchipelagoItem(static_cast(item)); +} void RandomizerOnFlagSetHandler(int16_t flagType, int16_t flag) { // Consume adult trade items @@ -254,11 +278,12 @@ void RandomizerOnFlagSetHandler(int16_t flagType, int16_t flag) { return; } auto loc = Rando::Context::GetInstance()->GetItemLocation(rc); - if (loc == nullptr || loc->HasObtained() || loc->GetPlacedRandomizerGet() == RG_NONE) { - Rando::Context::GetInstance()->GetItemLocation(rc)->SetCheckStatus(RCSHOW_COLLECTED); - return; + if (rc != RC_HF_OCARINA_OF_TIME_ITEM) { + if (loc == nullptr || loc->HasObtained() || loc->GetPlacedRandomizerGet() == RG_NONE) { + Rando::Context::GetInstance()->GetItemLocation(rc)->SetCheckStatus(RCSHOW_COLLECTED); + return; + } } - SPDLOG_INFO("Queuing RC: {}", static_cast(rc)); randomizerQueuedChecks.push(rc); } @@ -289,8 +314,76 @@ void RandomizerOnSceneFlagSetHandler(int16_t sceneNum, int16_t flagType, int16_t randomizerQueuedChecks.push(rc); } +void RandomizerOnExternalCheckHandler(uint32_t randomizerCheck) { + RandomizerCheck rc = static_cast(randomizerCheck); + Rando::Location* loc = Rando::StaticData::GetLocation(rc); + s32 flagID = loc->GetCollectionCheck().flag; + SceneID scene = loc->GetScene(); + + bool inSameArea = false; + if (gPlayState != nullptr) { + inSameArea = scene == gPlayState->sceneNum; + } + + // Receiving the OoT or Iron Boots chest locations without preventing the locations locked behind them. + // OoT locks Song of Time, Iron boots chest locks the song from Sheik in Ice Cavern. + // We also skip the bow chest in Forest Temple as the chest would spawn while the Stalfos fight still happens, + // leading to a double chest spawn. + if (rc == RC_HF_OCARINA_OF_TIME_ITEM || rc == RC_ICE_CAVERN_IRON_BOOTS_CHEST || rc == RC_FOREST_TEMPLE_BOW_CHEST) { + randomizerQueuedChecks.push(rc); + return; + } + + std::string logMessage = ""; + + switch (loc->GetCollectionCheck().type) { + case SPOILER_CHK_CHEST: + if (inSameArea) { + Flags_SetTreasure(gPlayState, flagID); + } else { + gSaveContext.sceneFlags[scene].chest |= 1 << flagID; + randomizerQueuedChecks.push(rc); + } + break; + case SPOILER_CHK_COLLECTABLE: + if (inSameArea) { + Flags_SetCollectible(gPlayState, flagID); + } else { + gSaveContext.sceneFlags[scene].collect |= 1 << flagID; + randomizerQueuedChecks.push(rc); + } + break; + case SPOILER_CHK_RANDOMIZER_INF: + Flags_SetRandomizerInf(static_cast(flagID)); + break; + case SPOILER_CHK_EVENT_CHK_INF: + Flags_SetEventChkInf(flagID); + break; + case SPOILER_CHK_ITEM_GET_INF: + Flags_SetItemGetInf(flagID); + break; + case SPOILER_CHK_INF_TABLE: + Flags_SetInfTable(flagID); + break; + case SPOILER_CHK_GOLD_SKULLTULA: + randomizerQueuedChecks.push(rc); + // Below doesn't work, temporarily disabled until a solution is found + // SET_GS_FLAGS((flagID & 0x1F00) >> 8, flagID & 0xFF); + break; + case SPOILER_CHK_GRAVEDIGGER: // This enum is used nowhere in code, so i'll leave it as nothing for now + case SPOILER_CHK_NONE: + // Do nothing + break; + } +} + static Vec3f spawnPos = { 0.0f, -999.0f, 0.0f }; +static void SkipGI_LogEntry(const char* tag, const GetItemEntry& e) { + SPDLOG_INFO("[SkipGI][{}] modIndex={} itemId={} getItemId={} drawModIndex={} drawItemId={} itemCat={}", tag, + e.modIndex, e.itemId, e.getItemId, e.drawModIndex, e.drawItemId, (int)e.getItemCategory); +} + void RandomizerOnPlayerUpdateForRCQueueHandler() { // If we're already queued, don't queue again if (randomizerQueuedCheck != RC_UNKNOWN_CHECK) @@ -307,12 +400,29 @@ void RandomizerOnPlayerUpdateForRCQueueHandler() { return; } + GetItemEntry getItemEntry; RandomizerCheck rc = randomizerQueuedChecks.front(); auto loc = Rando::Context::GetInstance()->GetItemLocation(rc); - RandomizerGet vanillaRandomizerGet = Rando::StaticData::GetLocation(rc)->GetVanillaItem(); - GetItemID vanillaItem = (GetItemID)Rando::StaticData::RetrieveItem(vanillaRandomizerGet).GetItemID(); - GetItemEntry getItemEntry = - Rando::Context::GetInstance()->GetFinalGIEntry(rc, true, (GetItemID)vanillaRandomizerGet); + uint8_t isGiSkipped = 0; + + if (rc == RC_ARCHIPELAGO_RECEIVED_ITEM) { + getItemEntry = Rando::Context::GetInstance()->GetArchipelagoGIEntry(); + } else { + RandomizerGet vanillaRandomizerGet = Rando::StaticData::GetLocation(rc)->GetVanillaItem(); + GetItemID vanillaItem = (GetItemID)Rando::StaticData::RetrieveItem(vanillaRandomizerGet).GetItemID(); + getItemEntry = Rando::Context::GetInstance()->GetFinalGIEntry(rc, true, vanillaItem); + } + + // When Ocarina or Iron Boots chest has been received externally before, and then picked up in the game itself, + // it'll be skipped and not give the song properly because the RC is already checked off. So instead we handle + // it here and send out the check locked behind them manually. + if (loc->HasObtained()) { + if (rc == RC_HF_OCARINA_OF_TIME_ITEM) { + RandomizerOnExternalCheckHandler(RC_SONG_FROM_OCARINA_OF_TIME); + } else if (rc == RC_ICE_CAVERN_IRON_BOOTS_CHEST) { + RandomizerOnExternalCheckHandler(RC_SHEIK_IN_ICE_CAVERN); + } + } if (loc->HasObtained()) { SPDLOG_INFO("RC {} already obtained, skipping", static_cast(rc)); @@ -322,30 +432,84 @@ void RandomizerOnPlayerUpdateForRCQueueHandler() { randomizerQueuedItemEntry = getItemEntry; SPDLOG_INFO("Queueing Item mod {} item {} from RC {}", getItemEntry.modIndex, getItemEntry.itemId, static_cast(rc)); + + // NEW: cache skip mode & ice trap flag + s32 skipMode = CVarGetInteger(CVAR_RANDOMIZER_ENHANCEMENT("TimeSavers.SkipGetItemAnimation"), SGIA_JUNK); + const bool isIceTrap = (getItemEntry.modIndex == MOD_RANDOMIZER && getItemEntry.getItemId == RG_ICE_TRAP); + + const GetItemEntry& logicEntry = getItemEntry; + GetItemEntry visualEntry = logicEntry; + + if (isIceTrap) { + sLastIceTrapCheck = rc; + RandomizerGet looksLike = Rando::Context::GetInstance()->GetLooksLikeForCheck(rc); + if (looksLike != RG_NONE) { + // Build a real entry for the disguised RG (this preserves boss souls / bean pack etc.) + visualEntry = Rando::StaticData::RetrieveItem(looksLike).GetGIEntry_Copy(); + + // Force the disguise entry to be interpreted as Randomizer/RG for SkipGI classification. + visualEntry.modIndex = MOD_RANDOMIZER; + visualEntry.getItemId = (GetItemID)looksLike; + visualEntry.drawModIndex = MOD_RANDOMIZER; + visualEntry.drawItemId = (GetItemID)looksLike; + + SPDLOG_INFO("[SkipGI][CALLSITE] rc={} skipMode={} isIceTrap={} looksLikeRG={}", (uint32_t)rc, + (int)skipMode, (int)isIceTrap, + (int)(isIceTrap ? Rando::Context::GetInstance()->GetLooksLikeForCheck(rc) : RG_NONE)); + + SkipGI_LogEntry("CALLSITE_LOGIC", logicEntry); + if (isIceTrap) { + SkipGI_LogEntry("CALLSITE_VISUAL", visualEntry); + } + } + } + + // Ice Trap override: if the Ice Traps category toggle is ON, it wins. + const bool iceTrapOverrideEnabled = + (skipMode == SGIA_ADVANCED) && + (CVarGetInteger(CVAR_RANDOMIZER_ENHANCEMENT("TimeSavers.SkipGetItemAnimationAdvanced.IceTraps"), 0) != 0); + if ( // Skipping ItemGet animation incompatible with checks that require closing a text box to finish - rc != RC_HF_OCARINA_OF_TIME_ITEM && rc != RC_SPIRIT_TEMPLE_SILVER_GAUNTLETS_CHEST && - rc != RC_MARKET_BOMBCHU_BOWLING_FIRST_PRIZE && rc != RC_MARKET_BOMBCHU_BOWLING_SECOND_PRIZE && - // Always show ItemGet animation for ice traps - !(getItemEntry.modIndex == MOD_RANDOMIZER && getItemEntry.getItemId == RG_ICE_TRAP) && + !(rc == RC_HF_OCARINA_OF_TIME_ITEM && gPlayState->sceneNum == SCENE_HYRULE_FIELD) && + !(rc == RC_SPIRIT_TEMPLE_SILVER_GAUNTLETS_CHEST && gPlayState->sceneNum == SCENE_DESERT_COLOSSUS) && + !(rc == RC_MARKET_BOMBCHU_BOWLING_FIRST_PRIZE && gPlayState->sceneNum == SCENE_BOMBCHU_BOWLING_ALLEY) && + !(rc == RC_MARKET_BOMBCHU_BOWLING_SECOND_PRIZE && gPlayState->sceneNum == SCENE_BOMBCHU_BOWLING_ALLEY) && + // Always show ItemGet animation for ice traps in legacy modes. + // Advanced mode is allowed to override this. + (skipMode == SGIA_ADVANCED || !isIceTrap) && // Always show ItemGet animation outside of randomizer to keep behaviour consistent in vanilla IS_RANDO && - (CVarGetInteger(CVAR_RANDOMIZER_ENHANCEMENT("TimeSavers.SkipGetItemAnimation"), SGIA_JUNK) == SGIA_ALL || - (CVarGetInteger(CVAR_RANDOMIZER_ENHANCEMENT("TimeSavers.SkipGetItemAnimation"), SGIA_JUNK) == SGIA_JUNK && - ( - // crude fix to ensure map hints are readable. Ideally replace with better hint tracking. - !(getItemEntry.getItemId >= RG_DEKU_TREE_MAP && getItemEntry.getItemId <= RG_ICE_CAVERN_MAP && - getItemEntry.modIndex == MOD_RANDOMIZER) && - (getItemEntry.getItemCategory == ITEM_CATEGORY_JUNK || - getItemEntry.getItemCategory == ITEM_CATEGORY_SKULLTULA_TOKEN || - getItemEntry.getItemCategory == ITEM_CATEGORY_LESSER || - // Treat small keys as junk if Skeleton Key is obtained. - (getItemEntry.getItemCategory == ITEM_CATEGORY_SMALL_KEY && - Flags_GetRandomizerInf(RAND_INF_HAS_SKELETON_KEY))))))) { + ( + // ADVANCED: use visual entry ONLY for ice traps (because disguise controls category) + (skipMode == SGIA_ADVANCED && + ((isIceTrap && iceTrapOverrideEnabled) || + ShouldSkipGetItemAnimationAdvanced(isIceTrap ? visualEntry : logicEntry))) + + || + + // OLD: existing behavior for None / Junk / All is kept exactly as-is + (skipMode != SGIA_ADVANCED && + (skipMode == SGIA_ALL || + (skipMode == SGIA_JUNK && + ( + // crude fix to ensure map hints are readable. Ideally replace with better hint tracking. + !(getItemEntry.getItemId >= RG_DEKU_TREE_MAP && getItemEntry.getItemId <= RG_ICE_CAVERN_MAP && + getItemEntry.modIndex == MOD_RANDOMIZER) && + (getItemEntry.getItemCategory == ITEM_CATEGORY_JUNK || + getItemEntry.getItemCategory == ITEM_CATEGORY_SKULLTULA_TOKEN || + getItemEntry.getItemCategory == ITEM_CATEGORY_LESSER || + // Treat small keys as junk if Skeleton Key is obtained. + (getItemEntry.getItemCategory == ITEM_CATEGORY_SMALL_KEY && + Flags_GetRandomizerInf(RAND_INF_HAS_SKELETON_KEY))))))))) { Item_DropCollectible(gPlayState, &spawnPos, static_cast(ITEM00_SOH_GIVE_ITEM_ENTRY | 0x8000)); + + isGiSkipped = 1; } } + GameInteractor_ExecuteOnRandomizerItemGivenHooks((uint32_t)rc, getItemEntry, isGiSkipped); + randomizerQueuedChecks.pop(); } @@ -379,6 +543,7 @@ void RandomizerOnItemReceiveHandler(GetItemEntry receivedItemEntry) { randomizerQueuedItemEntry.itemId == receivedItemEntry.itemId) { SPDLOG_INFO("Item received mod {} item {} from RC {}", receivedItemEntry.modIndex, receivedItemEntry.itemId, static_cast(randomizerQueuedCheck)); + loc->SetCheckStatus(RCSHOW_COLLECTED); CheckTracker::SpoilAreaFromCheck(randomizerQueuedCheck); CheckTracker::RecalculateAllAreaTotals(); @@ -451,6 +616,10 @@ void EnExItem_WaitForObjectRandomized(EnExItem* enExItem, PlayState* play) { } void EnItem00_DrawRandomizedItem(EnItem00* enItem00, PlayState* play) { + // Don't draw overhead items in cutscenes so they don't overlap during GI animations. + if (enItem00->actor.params == ITEM00_SOH_GIVE_ITEM_ENTRY && GameInteractor::IsGameplayPaused()) { + return; + } f32 mtxScale = CVarGetFloat(CVAR_RANDOMIZER_ENHANCEMENT("TimeSavers.SkipGetItemAnimationScale"), 10.0f); Matrix_Scale(mtxScale, mtxScale, mtxScale, MTXMODE_APPLY); GetItemEntry randoItem = enItem00->itemEntry; @@ -458,6 +627,24 @@ void EnItem00_DrawRandomizedItem(EnItem00* enItem00, PlayState* play) { enItem00->actor.params != ITEM00_SOH_GIVE_ITEM_ENTRY) { randoItem = GET_ITEM_MYSTERY; } + if (enItem00->actor.params == ITEM00_SOH_GIVE_ITEM_ENTRY && randoItem.modIndex == MOD_RANDOMIZER && + randoItem.getItemId == RG_ICE_TRAP) { + iceTrapScale = 0.8f; + OPEN_DISPS(play->state.gfxCtx); + Matrix_Push(); + Gfx_SetupDL_25Xlu(play->state.gfxCtx); + gSPSegment(POLY_XLU_DISP++, 0x08, + (uintptr_t)Gfx_TwoTexScroll(play->state.gfxCtx, 0, 0, (0 - play->gameplayFrames) % 128, 32, 32, 1, 0, + (play->gameplayFrames * -2) % 128, 32, 32)); + + Matrix_Translate(0.0f, -40.0f, 0.0f, MTXMODE_APPLY); + Matrix_Scale(iceTrapScale, iceTrapScale, iceTrapScale, MTXMODE_APPLY); + gSPMatrix(POLY_XLU_DISP++, MATRIX_NEWMTX(play->state.gfxCtx), G_MTX_NOPUSH | G_MTX_LOAD | G_MTX_MODELVIEW); + gDPSetEnvColor(POLY_XLU_DISP++, 0, 50, 100, 255); + gSPDisplayList(POLY_XLU_DISP++, (Gfx*)gEffIceFragment3DL); + Matrix_Pop(); + CLOSE_DISPS(play->state.gfxCtx); + } func_8002EBCC(&enItem00->actor, play, 0); func_8002ED80(&enItem00->actor, play, 0); EnItem00_CustomItemsParticles(&enItem00->actor, play, randoItem); @@ -794,6 +981,8 @@ void RandomizerOnVanillaBehaviorHandler(GIVanillaBehavior id, bool* should, va_l va_list args; va_copy(args, originalArgs); + u8 test; + switch (id) { case VB_ALLOW_ENTRANCE_CS_FOR_EITHER_AGE: { s32 entranceIndex = va_arg(args, s32); @@ -856,6 +1045,7 @@ void RandomizerOnVanillaBehaviorHandler(GIVanillaBehavior id, bool* should, va_l } break; case VB_MOVE_MIDO_IN_KOKIRI_FOREST: + test = RAND_GET_OPTION(RSK_FOREST); if (RAND_GET_OPTION(RSK_FOREST) == RO_CLOSED_FOREST_OFF && gSaveContext.cutsceneIndex == 0) { *should = true; } @@ -1044,42 +1234,55 @@ void RandomizerOnVanillaBehaviorHandler(GIVanillaBehavior id, bool* should, va_l message = "You found "; break; } - - Notification::Emit({ - .itemIcon = GetTextureForItemId(item00->itemEntry.itemId), - .message = message, - .suffix = SohUtils::GetItemName(item00->itemEntry.itemId), - }); + if (!(CVarGetInteger(CVAR_RANDOMIZER_ENHANCEMENT("NoJunkNotifications"), 0) && + item00->itemEntry.getItemCategory == ITEM_CATEGORY_JUNK)) { + Notification::Emit({ + .itemIcon = GetTextureForItemId(item00->itemEntry.itemId), + .message = message, + .suffix = SohUtils::GetItemName(item00->itemEntry.itemId), + }); + } } else if (item00->itemEntry.modIndex == MOD_RANDOMIZER) { std::string message; std::string itemName; - switch (gSaveContext.language) { - case LANGUAGE_FRA: - message = "Vous obtenez: "; - itemName = Rando::StaticData::RetrieveItem((RandomizerGet)item00->itemEntry.getItemId) - .GetName() - .french; - break; - case LANGUAGE_GER: - message = "Du erhältst: "; - itemName = Rando::StaticData::RetrieveItem((RandomizerGet)item00->itemEntry.getItemId) - .GetName() - .german; - break; - case LANGUAGE_ENG: - default: - message = "You found "; - itemName = Rando::StaticData::RetrieveItem((RandomizerGet)item00->itemEntry.getItemId) - .GetName() - .english; - break; - } + if (!(item00->itemEntry.getItemId == RG_ARCHIPELAGO_ITEM_PROGRESSIVE || + item00->itemEntry.getItemId == RG_ARCHIPELAGO_ITEM_USEFUL || + item00->itemEntry.getItemId == RG_ARCHIPELAGO_ITEM_JUNK)) { + + switch (gSaveContext.language) { + case LANGUAGE_FRA: + message = "Vous obtenez: "; + itemName = Rando::StaticData::RetrieveItem((RandomizerGet)item00->itemEntry.getItemId) + .GetName() + .french; + break; + case LANGUAGE_GER: + message = "Du erhältst: "; + itemName = Rando::StaticData::RetrieveItem((RandomizerGet)item00->itemEntry.getItemId) + .GetName() + .german; + break; + case LANGUAGE_ENG: + default: + message = "You found "; + itemName = Rando::StaticData::RetrieveItem((RandomizerGet)item00->itemEntry.getItemId) + .GetName() + .english; + break; + } - Notification::Emit({ - .message = message, - .suffix = itemName, - }); + if (CVarGetInteger("gHoliday.Visual.HolidayPieces", 0)) { + if ((RandomizerGet)item00->itemEntry.getItemId == RG_TRIFORCE_PIECE) { + itemName = "a Christmas Ornament"; + } + } + + Notification::Emit({ + .message = message, + .suffix = itemName, + }); + } } // This is typically called when you close the text box after getting an item, in case a previous @@ -2088,7 +2291,8 @@ void RandomizerOnActorInitHandler(void* actorRef) { } if (actor->id == ACTOR_BG_TREEMOUTH && LINK_IS_ADULT && - RAND_GET_OPTION(RSK_SHUFFLE_DUNGEON_ENTRANCES) != RO_DUNGEON_ENTRANCE_SHUFFLE_OFF && + (RAND_GET_OPTION(RSK_SHUFFLE_DUNGEON_ENTRANCES) != RO_DUNGEON_ENTRANCE_SHUFFLE_OFF || + CVarGetInteger(CVAR_REMOTE_ARCHIPELAGO("ShuffleEntrances"), 0)) && (RAND_GET_OPTION(RSK_FOREST) == RO_CLOSED_FOREST_OFF || Flags_GetEventChkInf(EVENTCHKINF_SHOWED_MIDO_SWORD_SHIELD))) { BgTreemouth* bgTreemouth = static_cast(actorRef); @@ -2267,8 +2471,9 @@ void RandomizerOnActorUpdateHandler(void* refActor) { } // In ER, override the warp song locations. Also removes the warp song cutscene - if (RAND_GET_OPTION(RSK_SHUFFLE_ENTRANCES) && actor->id == ACTOR_DEMO_KANKYO && - actor->params == 0x000F) { // Warp Song particles + if ((RAND_GET_OPTION(RSK_SHUFFLE_ENTRANCES) || + (CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipMiscInteractions"), 0))) && + actor->id == ACTOR_DEMO_KANKYO && actor->params == 0x000F) { // Warp Song particles Entrance_SetWarpSongEntrance(); } } @@ -2357,6 +2562,14 @@ void RandomizerOnPlayerUpdateHandler() { GameInteractor::State::TriforceHuntPieceGiven = 0; } } + if (!Flags_GetRandomizerInf(RAND_INF_CAN_GROUND_JUMP)) { + if (GET_PLAYER(gPlayState)->stateFlags1 & PLAYER_STATE1_CARRYING_ACTOR && + GET_PLAYER(gPlayState)->stateFlags1 & PLAYER_STATE1_SHIELDING) { + if (GET_PLAYER(gPlayState)->upperActionFunc == func_80834BD4 && GET_PLAYER(gPlayState)->heldActor == NULL) { + GET_PLAYER(gPlayState)->stateFlags1 &= ~PLAYER_STATE1_CARRYING_ACTOR; + } + } + } } void RandomizerOnSceneSpawnActorsHandler() { @@ -2418,6 +2631,87 @@ void RandomizerOnCuccoOrChickenHatch() { } } +uint32_t RandomizerReturnCurrentlyQueuedItem() { + return (uint32_t)randomizerQueuedCheck; +} + +void RandomizerOnLinkAnimEnd(SkelAnime* skelAnime) { + if (!Flags_GetRandomizerInf(RAND_INF_CAN_ISG)) { + Player* player = GET_PLAYER(gPlayState); + + // Make sure we are only checking for the end of link's animation + // TODO: Use gPlayerAnim_link_normal_defense_kiru? + if (skelAnime == &player->skelAnime && player->meleeWeaponAnimation == PLAYER_MWA_STAB_1H) { + func_80832318(player); + } + } +} + +void RandomizerShouldSkipForcePlayOcarina(bool* should) { + + if (!Flags_GetRandomizerInf(RAND_INF_CAN_OI)) { + Player* player = GET_PLAYER(gPlayState); + + if (player->itemAction != PLAYER_IA_OCARINA_FAIRY && player->itemAction != PLAYER_IA_OCARINA_OF_TIME) { + player->unk_6AD = 0; + Sfx_PlaySfxCentered(NA_SE_SY_ERROR); + Player_SetupActionPreserveItemAction(gPlayState, player, Player_Action_Idle, 0); + player->stateFlags1 &= ~PLAYER_STATE1_IN_CUTSCENE; + *should = true; + } + } +} + +void RandomizerOnQPADamage(uint32_t* dmgFlags) { + if (!Flags_GetRandomizerInf(RAND_INF_CAN_QPA)) { + *dmgFlags = 0; + } +} + +void RandomizerOnESS() { + if (!Flags_GetRandomizerInf(RAND_INF_CAN_HESS)) { + Player_DecelerateToZero(GET_PLAYER(gPlayState)); + } +} + +void RandomizerOnWaitForPutaway() { + if (!Flags_GetRandomizerInf(RAND_INF_CAN_SUPERSLIDE)) { + Player_DecelerateToZero(GET_PLAYER(gPlayState)); + } +} + +void RandomizerShouldHover(bool* should) { + if (!Flags_GetRandomizerInf(RAND_INF_CAN_HOVER)) { + *should = false; + } +} + +void RandomizerOnKaleidoMoveCursorFromSpecialPos(PauseContext* pauseCtx, uint16_t* cursorItem) { + if (!Flags_GetRandomizerInf(RAND_INF_CAN_EQUIP_SWAP)) { + *cursorItem = PAUSE_ITEM_NONE; + // PAUSE_ITEM_NONE feels more accurate to intended behaviour, but alternative here also works + // *cursorItem = gSaveContext.inventory.items[pauseCtx->cursorPoint[PAUSE_ITEM]]; + } +} + +void RandomizerOnAnimationSetLoadFrame(LinkAnimationHeader* animation, int32_t* frame) { + if (!Flags_GetRandomizerInf(RAND_INF_CAN_WEIRDSHOT)) { + std::optional animationName; + + if (ResourceMgr_OTRSigCheck(reinterpret_cast(animation)) != 0) { + animationName = reinterpret_cast(animation); + animation = reinterpret_cast(ResourceMgr_LoadAnimByName(*animationName)); + } + + const auto playerAnimHeader = + static_cast(SEGMENTED_TO_VIRTUAL(static_cast(animation))); + + if (*frame < 0 || *frame >= playerAnimHeader->common.frameCount) { + *frame = 0; + } + } +} + void RandomizerRegisterHooks() { static uint32_t onFlagSetHook = 0; static uint32_t onSceneFlagSetHook = 0; @@ -2437,6 +2731,12 @@ void RandomizerRegisterHooks() { static uint32_t onExitGameHook = 0; static uint32_t onKaleidoUpdateHook = 0; static uint32_t onCuccoOrChickenHatchHook = 0; + static uint32_t onLinkAnimEndHook = 0; + static uint32_t onQPADamageHook = 0; + static uint32_t onESSHook = 0; + static uint32_t onWaitForPutawayHook = 0; + static uint32_t onKaleidoMoveCursorFromSpecialPosHook = 0; + static uint32_t onAnimationSetLoadFrameHook = 0; static uint32_t fishsanityOnActorInitHook = 0; static uint32_t fishsanityOnActorUpdateHook = 0; @@ -2447,6 +2747,9 @@ void RandomizerRegisterHooks() { GameInteractor::Instance->RegisterGameHook([](int32_t fileNum) { ShipInit::Init("IS_RANDO"); + // Add condition around this to only fire when loading into an Archipelago save file + ShipInit::Init("IS_ARCHIPELAGO"); + randomizerQueuedChecks = std::queue(); randomizerQueuedCheck = RC_UNKNOWN_CHECK; randomizerQueuedItemEntry = GET_ITEM_NONE; @@ -2469,6 +2772,14 @@ void RandomizerRegisterHooks() { GameInteractor::Instance->UnregisterGameHook(onExitGameHook); GameInteractor::Instance->UnregisterGameHook(onKaleidoUpdateHook); GameInteractor::Instance->UnregisterGameHook(onCuccoOrChickenHatchHook); + GameInteractor::Instance->UnregisterGameHook(onLinkAnimEndHook); + GameInteractor::Instance->UnregisterGameHook(onQPADamageHook); + GameInteractor::Instance->UnregisterGameHook(onESSHook); + GameInteractor::Instance->UnregisterGameHook(onWaitForPutawayHook); + GameInteractor::Instance->UnregisterGameHook( + onKaleidoMoveCursorFromSpecialPosHook); + GameInteractor::Instance->UnregisterGameHook( + onAnimationSetLoadFrameHook); GameInteractor::Instance->UnregisterGameHook(fishsanityOnActorInitHook); GameInteractor::Instance->UnregisterGameHook(fishsanityOnActorUpdateHook); @@ -2495,6 +2806,7 @@ void RandomizerRegisterHooks() { onExitGameHook = 0; onKaleidoUpdateHook = 0; onCuccoOrChickenHatchHook = 0; + onLinkAnimEndHook = 0; fishsanityOnActorInitHook = 0; fishsanityOnActorUpdateHook = 0; @@ -2550,6 +2862,30 @@ void RandomizerRegisterHooks() { RandomizerOnKaleidoscopeUpdateHandler); onCuccoOrChickenHatchHook = GameInteractor::Instance->RegisterGameHook( RandomizerOnCuccoOrChickenHatch); + onLinkAnimEndHook = GameInteractor::Instance->RegisterGameHook( + [](SkelAnime* skelAnime) { RandomizerOnLinkAnimEnd(skelAnime); }); + onQPADamageHook = GameInteractor::Instance->RegisterGameHook( + [](uint32_t* dmgFlags) { RandomizerOnQPADamage(dmgFlags); }); + onESSHook = GameInteractor::Instance->RegisterGameHook(RandomizerOnESS); + onWaitForPutawayHook = + GameInteractor::Instance->RegisterGameHook(RandomizerOnWaitForPutaway); + onKaleidoMoveCursorFromSpecialPosHook = + GameInteractor::Instance->RegisterGameHook( + [](PauseContext* pauseCtx, uint16_t* cursorItem) { + RandomizerOnKaleidoMoveCursorFromSpecialPos(pauseCtx, cursorItem); + }); + onAnimationSetLoadFrameHook = + GameInteractor::Instance->RegisterGameHook( + [](LinkAnimationHeader* animation, int32_t* frame) { + RandomizerOnAnimationSetLoadFrame(animation, frame); + }); + + COND_VB_SHOULD(VB_SKIP_FORCE_PLAY_OCARINA, true, { RandomizerShouldSkipForcePlayOcarina(should); }); + + COND_VB_SHOULD(VB_HOVER_WITH_ISG, true, { RandomizerShouldHover(should); }); + + COND_HOOK(GameInteractor::OnArchipelagoItemReceived, IS_ARCHIPELAGO, ArchipelagoOnReceiveItem); + COND_HOOK(GameInteractor::OnRandomizerExternalCheck, IS_ARCHIPELAGO, RandomizerOnExternalCheckHandler) if (RAND_GET_OPTION(RSK_FISHSANITY) != RO_FISHSANITY_OFF) { OTRGlobals::Instance->gRandoContext->GetFishsanity()->InitializeFromSave(); diff --git a/soh/soh/Enhancements/randomizer/hook_handlers.h b/soh/soh/Enhancements/randomizer/hook_handlers.h index 46abb30b72c..046125d859c 100644 --- a/soh/soh/Enhancements/randomizer/hook_handlers.h +++ b/soh/soh/Enhancements/randomizer/hook_handlers.h @@ -2,5 +2,6 @@ #define RANDOMIZER_HOOK_HANDLERS_H void RandomizerRegisterHooks(); +uint32_t RandomizerReturnCurrentlyQueuedItem(); #endif // RANDOMIZER_HOOK_HANDLERS_H \ No newline at end of file diff --git a/soh/soh/Enhancements/randomizer/item_list.cpp b/soh/soh/Enhancements/randomizer/item_list.cpp index 9aa9b140096..3f411cefc21 100644 --- a/soh/soh/Enhancements/randomizer/item_list.cpp +++ b/soh/soh/Enhancements/randomizer/item_list.cpp @@ -351,7 +351,26 @@ void Rando::StaticData::InitItemTable() { itemTable[RG_BRONZE_SCALE] = Item(RG_BRONZE_SCALE, Text{ "Bronze Scale", "Écaille de Bronze", "Bronzene Schuppe" }, ITEMTYPE_ITEM, GI_SCALE_SILVER, true, LOGIC_PROGRESSIVE_WALLET, RHT_BRONZE_SCALE, RG_BRONZE_SCALE, OBJECT_GI_SCALE, GID_SCALE_SILVER, TEXT_RANDOMIZER_CUSTOM_ITEM, 0x80, CHEST_ANIM_LONG, ITEM_CATEGORY_MAJOR, MOD_RANDOMIZER); itemTable[RG_BRONZE_SCALE].SetCustomDrawFunc(Randomizer_DrawBronzeScale); - itemTable[RG_PROGRESSIVE_BOMBCHU_BAG] = Item(RG_PROGRESSIVE_BOMBCHU_BAG, Text{ "Bombchu Bag", "Sac de Missiles Teigneux", "Krabbelminentasche" }, ITEMTYPE_ITEM, RG_PROGRESSIVE_BOMBCHU_BAG, true, LOGIC_BOMBCHUS, RHT_BOMBCHU_BAG, RG_PROGRESSIVE_BOMBCHU_BAG, OBJECT_GI_BOMB_2, GID_BOMBCHU, TEXT_RANDOMIZER_CUSTOM_ITEM, 0x80, CHEST_ANIM_LONG, ITEM_CATEGORY_MAJOR, MOD_RANDOMIZER); + itemTable[RG_ABILITY_ISG] = Item(RG_ABILITY_ISG, Text{ "ISG", "ISG", "ISG" }, ITEMTYPE_ITEM, GI_NONE, true, LOGIC_NONE, RHT_ABILITY_ISG, RG_ABILITY_ISG, OBJECT_GI_COIN, GID_NCOIN_YELLOW, TEXT_RANDOMIZER_CUSTOM_ITEM, 0x80, CHEST_ANIM_LONG, ITEM_CATEGORY_MAJOR, MOD_RANDOMIZER); + itemTable[RG_ABILITY_ISG].SetCustomDrawFunc(Randomizer_DrawMysteryItem); + itemTable[RG_ABILITY_OI] = Item(RG_ABILITY_OI, Text{ "OI", "OI", "OI" }, ITEMTYPE_ITEM, GI_NONE, true, LOGIC_NONE, RHT_ABILITY_OI, RG_ABILITY_OI, OBJECT_GI_COIN, GID_NCOIN_YELLOW, TEXT_RANDOMIZER_CUSTOM_ITEM, 0x80, CHEST_ANIM_LONG, ITEM_CATEGORY_MAJOR, MOD_RANDOMIZER); + itemTable[RG_ABILITY_OI].SetCustomDrawFunc(Randomizer_DrawMysteryItem); + itemTable[RG_ABILITY_QPA] = Item(RG_ABILITY_QPA, Text{ "QPA", "QPA", "QPA" }, ITEMTYPE_ITEM, GI_NONE, true, LOGIC_NONE, RHT_ABILITY_QPA, RG_ABILITY_QPA, OBJECT_GI_COIN, GID_NCOIN_YELLOW, TEXT_RANDOMIZER_CUSTOM_ITEM, 0x80, CHEST_ANIM_LONG, ITEM_CATEGORY_MAJOR, MOD_RANDOMIZER); + itemTable[RG_ABILITY_QPA].SetCustomDrawFunc(Randomizer_DrawMysteryItem); + itemTable[RG_ABILITY_HESS] = Item(RG_ABILITY_HESS, Text{ "Extended Superslide", "Extended Superslide", "Extended Superslide" }, ITEMTYPE_ITEM, GI_NONE, true, LOGIC_NONE, RHT_ABILITY_HESS, RG_ABILITY_HESS, OBJECT_GI_COIN, GID_NCOIN_YELLOW, TEXT_RANDOMIZER_CUSTOM_ITEM, 0x80, CHEST_ANIM_LONG, ITEM_CATEGORY_MAJOR, MOD_RANDOMIZER); + itemTable[RG_ABILITY_HESS].SetCustomDrawFunc(Randomizer_DrawMysteryItem); + itemTable[RG_ABILITY_SUPERSLIDE] = Item(RG_ABILITY_SUPERSLIDE, Text{ "Superslide", "Superslide", "Superslide" }, ITEMTYPE_ITEM, GI_NONE, true, LOGIC_NONE, RHT_ABILITY_SUPERSLIDE, RG_ABILITY_SUPERSLIDE, OBJECT_GI_COIN, GID_NCOIN_YELLOW, TEXT_RANDOMIZER_CUSTOM_ITEM, 0x80, CHEST_ANIM_LONG, ITEM_CATEGORY_MAJOR, MOD_RANDOMIZER); + itemTable[RG_ABILITY_SUPERSLIDE].SetCustomDrawFunc(Randomizer_DrawMysteryItem); + itemTable[RG_ABILITY_HOVER] = Item(RG_ABILITY_HOVER, Text{ "Hovering", "Hovering", "Hovering" }, ITEMTYPE_ITEM, GI_NONE, true, LOGIC_NONE, RHT_ABILITY_HOVER, RG_ABILITY_HOVER, OBJECT_GI_COIN, GID_NCOIN_YELLOW, TEXT_RANDOMIZER_CUSTOM_ITEM, 0x80, CHEST_ANIM_LONG, ITEM_CATEGORY_MAJOR, MOD_RANDOMIZER); + itemTable[RG_ABILITY_HOVER].SetCustomDrawFunc(Randomizer_DrawMysteryItem); + itemTable[RG_ABILITY_EQUIP_SWAP] = Item(RG_ABILITY_EQUIP_SWAP, Text{ "Equip Swap", "Equip Swap", "Equip Swap" }, ITEMTYPE_ITEM, GI_NONE, true, LOGIC_NONE, RHT_ABILITY_EQUIP_SWAP, RG_ABILITY_EQUIP_SWAP, OBJECT_GI_COIN, GID_NCOIN_YELLOW, TEXT_RANDOMIZER_CUSTOM_ITEM, 0x80, CHEST_ANIM_LONG, ITEM_CATEGORY_MAJOR, MOD_RANDOMIZER); + itemTable[RG_ABILITY_EQUIP_SWAP].SetCustomDrawFunc(Randomizer_DrawMysteryItem); + itemTable[RG_ABILITY_GROUND_JUMP] = Item(RG_ABILITY_GROUND_JUMP, Text{ "Ground Jump", "Ground Jump", "Ground Jump" }, ITEMTYPE_ITEM, GI_NONE, true, LOGIC_NONE, RHT_ABILITY_GROUND_JUMP, RG_ABILITY_GROUND_JUMP, OBJECT_GI_COIN, GID_NCOIN_YELLOW, TEXT_RANDOMIZER_CUSTOM_ITEM, 0x80, CHEST_ANIM_LONG, ITEM_CATEGORY_MAJOR, MOD_RANDOMIZER); + itemTable[RG_ABILITY_GROUND_JUMP].SetCustomDrawFunc(Randomizer_DrawMysteryItem); + itemTable[RG_ABILITY_WEIRDSHOT] = Item(RG_ABILITY_WEIRDSHOT, Text{ "Weirdshot", "Weirdshot", "Weirdshot" }, ITEMTYPE_ITEM, GI_NONE, true, LOGIC_NONE, RHT_ABILITY_WEIRDSHOT, RG_ABILITY_WEIRDSHOT, OBJECT_GI_COIN, GID_NCOIN_YELLOW, TEXT_RANDOMIZER_CUSTOM_ITEM, 0x80, CHEST_ANIM_LONG, ITEM_CATEGORY_MAJOR, MOD_RANDOMIZER); + itemTable[RG_ABILITY_WEIRDSHOT].SetCustomDrawFunc(Randomizer_DrawMysteryItem); + + itemTable[RG_PROGRESSIVE_BOMBCHU_BAG] = Item(RG_PROGRESSIVE_BOMBCHU_BAG, Text{ "Progressive Bombchu Bag", "Sac de Missiles Teigneux", "Krabbelminentasche" }, ITEMTYPE_ITEM, RG_PROGRESSIVE_BOMBCHU_BAG, true, LOGIC_BOMBCHUS, RHT_BOMBCHU_BAG, RG_PROGRESSIVE_BOMBCHU_BAG, OBJECT_GI_BOMB_2, GID_BOMBCHU, TEXT_RANDOMIZER_CUSTOM_ITEM, 0x80, CHEST_ANIM_LONG, ITEM_CATEGORY_MAJOR, MOD_RANDOMIZER); itemTable[RG_PROGRESSIVE_BOMBCHU_BAG].SetCustomDrawFunc(Randomizer_DrawBombchuBag); itemTable[RG_QUIVER_INF] = Item(RG_QUIVER_INF, Text{ "Infinite Quiver", "Carquois Infini", "Unendlicher Köcher" }, ITEMTYPE_ITEM, RG_QUIVER_INF, true, LOGIC_PROGRESSIVE_BOW, RHT_QUIVER_INF, RG_QUIVER_INF, OBJECT_GI_ARROWCASE, GID_QUIVER_50, TEXT_RANDOMIZER_CUSTOM_ITEM, 0x80, CHEST_ANIM_LONG, ITEM_CATEGORY_LESSER, MOD_RANDOMIZER); @@ -404,6 +423,14 @@ void Rando::StaticData::InitItemTable() { itemTable[RG_MAGIC_DOUBLE] = Item(RG_MAGIC_DOUBLE, Text{ "Enhanced Magic Meter", "Jauge de Magie améliorée", "Verb. Magische Kraft" }, ITEMTYPE_ITEM, 0x8A, true, LOGIC_PROGRESSIVE_MAGIC, RHT_MAGIC_DOUBLE, RG_MAGIC_DOUBLE, OBJECT_GI_MAGICPOT, GID_MAGIC_LARGE, 0xE8, 0x80, CHEST_ANIM_LONG, ITEM_CATEGORY_LESSER, MOD_RANDOMIZER); itemTable[RG_TRIFORCE_PIECE] = Item(RG_TRIFORCE_PIECE, Text{ "Triforce Piece", "Triforce Piece", "Triforce-Splitter" }, ITEMTYPE_ITEM, 0xDF, true, LOGIC_TRIFORCE_PIECES, RHT_TRIFORCE_PIECE, RG_TRIFORCE_PIECE, OBJECT_GI_BOMB_2, GID_TRIFORCE_PIECE, TEXT_RANDOMIZER_CUSTOM_ITEM, 0x80, CHEST_ANIM_LONG, ITEM_CATEGORY_MAJOR, MOD_RANDOMIZER); + // Archipelago + itemTable[RG_ARCHIPELAGO_ITEM_USEFUL] = Item(RG_ARCHIPELAGO_ITEM_USEFUL, Text{"Useful AP Item", "Useful AP Item", "Useful AP Item"}, ITEMTYPE_EVENT, GI_RUPEE_GREEN, false, LOGIC_NONE, RHT_NONE, RG_ARCHIPELAGO_ITEM_USEFUL, OBJECT_GI_LETTER, GID_LETTER_ZELDA, TEXT_RANDOMIZER_CUSTOM_ITEM, 0x80, CHEST_ANIM_SHORT, ITEM_CATEGORY_LESSER, MOD_RANDOMIZER); + itemTable[RG_ARCHIPELAGO_ITEM_USEFUL].SetCustomDrawFunc(Randomizer_DrawArchipelagoItem); + itemTable[RG_ARCHIPELAGO_ITEM_JUNK] = Item(RG_ARCHIPELAGO_ITEM_JUNK, Text{"Junk AP Item", "Junk AP Item", "Junk AP Item"}, ITEMTYPE_EVENT, GI_RUPEE_GREEN, false, LOGIC_NONE, RHT_NONE, RG_ARCHIPELAGO_ITEM_JUNK, OBJECT_GI_LETTER, GID_LETTER_ZELDA, TEXT_RANDOMIZER_CUSTOM_ITEM, 0x80, CHEST_ANIM_SHORT, ITEM_CATEGORY_JUNK, MOD_RANDOMIZER); + itemTable[RG_ARCHIPELAGO_ITEM_JUNK].SetCustomDrawFunc(Randomizer_DrawArchipelagoItem); + itemTable[RG_ARCHIPELAGO_ITEM_PROGRESSIVE] = Item(RG_ARCHIPELAGO_ITEM_PROGRESSIVE, Text{"Progressive AP Item", "Progressive AP Item", "Progressive AP Item"}, ITEMTYPE_EVENT, GI_RUPEE_GREEN, false, LOGIC_NONE, RHT_NONE, RG_ARCHIPELAGO_ITEM_PROGRESSIVE, OBJECT_GI_LETTER, GID_LETTER_ZELDA, TEXT_RANDOMIZER_CUSTOM_ITEM, 0x80, CHEST_ANIM_LONG, ITEM_CATEGORY_MAJOR, MOD_RANDOMIZER); + itemTable[RG_ARCHIPELAGO_ITEM_PROGRESSIVE].SetCustomDrawFunc(Randomizer_DrawArchipelagoItem); + // clang-format on // Init itemNameToEnum diff --git a/soh/soh/Enhancements/randomizer/item_location.cpp b/soh/soh/Enhancements/randomizer/item_location.cpp index b5325c604c2..c0b3b862ea7 100644 --- a/soh/soh/Enhancements/randomizer/item_location.cpp +++ b/soh/soh/Enhancements/randomizer/item_location.cpp @@ -133,6 +133,8 @@ bool ItemLocation::HasObtained() const { } void ItemLocation::SetCheckStatus(RandomizerCheckStatus status_) { + if (rc == RC_ARCHIPELAGO_RECEIVED_ITEM) // never count the AP receive trigger as 'collected' + return; status = status_; GameInteractor::Instance->ExecuteHooks(rc, status); } diff --git a/soh/soh/Enhancements/randomizer/location_access.cpp b/soh/soh/Enhancements/randomizer/location_access.cpp index 0f517aa3ff5..738c311a213 100644 --- a/soh/soh/Enhancements/randomizer/location_access.cpp +++ b/soh/soh/Enhancements/randomizer/location_access.cpp @@ -946,7 +946,7 @@ std::string CleanCheckConditionString(std::string condition) { } namespace Regions { -auto GetAllRegions() { +std::array GetAllRegions() { static const size_t regionCount = RR_MAX - (RR_NONE + 1); static std::array allRegions = {}; diff --git a/soh/soh/Enhancements/randomizer/location_access.h b/soh/soh/Enhancements/randomizer/location_access.h index d55928eea85..c567df781f2 100644 --- a/soh/soh/Enhancements/randomizer/location_access.h +++ b/soh/soh/Enhancements/randomizer/location_access.h @@ -4,6 +4,7 @@ #include #include #include +#include #include "soh/Enhancements/randomizer/randomizerTypes.h" #include "soh/Enhancements/randomizer/context.h" @@ -237,6 +238,7 @@ extern void AccessReset(); extern void ResetAllLocations(); extern bool HasTimePassAccess(uint8_t age); extern void DumpWorldGraph(std::string str); +extern std::array GetAllRegions(); } // namespace Regions void RegionTable_Init(); diff --git a/soh/soh/Enhancements/randomizer/logic.cpp b/soh/soh/Enhancements/randomizer/logic.cpp index b831d4c2755..7d1d4ddfa25 100644 --- a/soh/soh/Enhancements/randomizer/logic.cpp +++ b/soh/soh/Enhancements/randomizer/logic.cpp @@ -1821,6 +1821,33 @@ void Logic::ApplyItemEffect(Item& item, bool state) { case RG_BOMBCHU_20: SetInventory(ITEM_BOMBCHU, (!state ? ITEM_NONE : ITEM_BOMBCHU)); break; + case RG_ABILITY_ISG: + SetRandoInf(RAND_INF_CAN_ISG, state); + break; + case RG_ABILITY_OI: + SetRandoInf(RAND_INF_CAN_OI, state); + break; + case RG_ABILITY_QPA: + SetRandoInf(RAND_INF_CAN_QPA, state); + break; + case RG_ABILITY_HESS: + SetRandoInf(RAND_INF_CAN_HESS, state); + break; + case RG_ABILITY_SUPERSLIDE: + SetRandoInf(RAND_INF_CAN_SUPERSLIDE, state); + break; + case RG_ABILITY_HOVER: + SetRandoInf(RAND_INF_CAN_HOVER, state); + break; + case RG_ABILITY_EQUIP_SWAP: + SetRandoInf(RAND_INF_CAN_EQUIP_SWAP, state); + break; + case RG_ABILITY_GROUND_JUMP: + SetRandoInf(RAND_INF_CAN_GROUND_JUMP, state); + break; + case RG_ABILITY_WEIRDSHOT: + SetRandoInf(RAND_INF_CAN_WEIRDSHOT, state); + break; default: break; } @@ -2312,6 +2339,35 @@ void Logic::Reset(bool resetSaveContext /*= true*/) { SetRandoInf(RAND_INF_CAN_SWIM, true); } + // If we're not shuffling glitch abilites, we start with them + if (ctx->GetOption(RSK_SHUFFLE_ISG).Is(false)) { + SetRandoInf(RAND_INF_CAN_ISG, true); + } + if (ctx->GetOption(RSK_SHUFFLE_OI).Is(false)) { + SetRandoInf(RAND_INF_CAN_OI, true); + } + if (ctx->GetOption(RSK_SHUFFLE_QPA).Is(false)) { + SetRandoInf(RAND_INF_CAN_QPA, true); + } + if (ctx->GetOption(RSK_SHUFFLE_HESS).Is(false)) { + SetRandoInf(RAND_INF_CAN_HESS, true); + } + if (ctx->GetOption(RSK_SHUFFLE_SUPERSLIDE).Is(false)) { + SetRandoInf(RAND_INF_CAN_SUPERSLIDE, true); + } + if (ctx->GetOption(RSK_SHUFFLE_HOVER).Is(false)) { + SetRandoInf(RAND_INF_CAN_HOVER, true); + } + if (ctx->GetOption(RSK_SHUFFLE_EQUIP_SWAP).Is(false)) { + SetRandoInf(RAND_INF_CAN_EQUIP_SWAP, true); + } + if (ctx->GetOption(RSK_SHUFFLE_GROUND_JUMP).Is(false)) { + SetRandoInf(RAND_INF_CAN_GROUND_JUMP, true); + } + if (ctx->GetOption(RSK_SHUFFLE_WEIRDSHOT).Is(false)) { + SetRandoInf(RAND_INF_CAN_WEIRDSHOT, true); + } + // If we're not shuffling child's wallet, we start with it if (ctx->GetOption(RSK_SHUFFLE_CHILD_WALLET).Is(false)) { SetRandoInf(RAND_INF_HAS_WALLET, true); diff --git a/soh/soh/Enhancements/randomizer/option.cpp b/soh/soh/Enhancements/randomizer/option.cpp index bfdecd861da..87891844b91 100644 --- a/soh/soh/Enhancements/randomizer/option.cpp +++ b/soh/soh/Enhancements/randomizer/option.cpp @@ -125,9 +125,30 @@ bool Option::IsCategory(const OptionCategory category) const { return category == this->category; } +constexpr float LOCK_CHECKBOX_OFFSET_X = 20.0f; +constexpr float LOCK_CHECKBOX_OFFSET_Y = 0.0f; + +constexpr float LOCK_COMBO_OFFSET_X = 0.0f; +constexpr float LOCK_COMBO_OFFSET_Y = -18.0f; + +constexpr float LOCK_SLIDER_OFFSET_X = 0.0f; +constexpr float LOCK_SLIDER_OFFSET_Y = -20.0f; + +constexpr float LOCK_LOGIC_RULES_OFFSET_X = 4.0f; +constexpr float LOCK_LOGIC_RULES_OFFSET_Y = 0.0f; + +#define CVAR_RANDO_LOCKS_ENABLED CVAR_SETTING("RandoLocksEnabled") + +inline bool LocksEnabled() { + return CVarGetInteger(CVAR_RANDO_LOCKS_ENABLED, 0) != 0; +} + bool Option::RenderImGui() { bool changed = false; - ImGui::BeginGroup(); + + ImGui::BeginGroup(); // make widget+lock behave as a single item to the outside + + // 1. Draw the original widget exactly as normal switch (widgetType) { case WidgetType::Checkbox: changed = RenderCheckbox(); @@ -139,6 +160,73 @@ bool Option::RenderImGui() { changed = RenderSlider(); break; } + + // 2. Add lock icon (if this option has a CVar) + if (!cvarName.empty() && LocksEnabled()) { + bool locked = IsLocked(); + + // Safe ImGui APIs for last item rectangle + ImVec2 itemMin = ImGui::GetItemRectMin(); + ImVec2 itemMax = ImGui::GetItemRectMax(); + ImVec2 itemSize = ImGui::GetItemRectSize(); + + float iconSize = ImGui::GetTextLineHeight(); + float textWidth = ImGui::CalcTextSize(name.c_str()).x; + float spacing = ImGui::GetStyle().ItemInnerSpacing.x; + + ImVec2 cursorBackup = ImGui::GetCursorScreenPos(); + ImVec2 iconPos{ itemMin.x, itemMin.y }; + + if (widgetType == WidgetType::Checkbox) { + // Checkbox: [box][space][label][space][lock] + float boxSize = iconSize; + + iconPos.x = itemMin.x + boxSize + spacing + textWidth + spacing + LOCK_CHECKBOX_OFFSET_X; + iconPos.y = itemMin.y + (itemSize.y - iconSize) * 0.5f + LOCK_CHECKBOX_OFFSET_Y; + + } else if (widgetType == WidgetType::Combobox) { + if (GetKey() == RSK_LOGIC_RULES) { + // Special case: Logic Rules combo put lock to the RIGHT of the combo box + iconPos.x = itemMax.x + spacing + LOCK_LOGIC_RULES_OFFSET_X; + iconPos.y = itemMin.y + (itemSize.y - iconSize) * 0.5f + LOCK_LOGIC_RULES_OFFSET_Y; + } else { + // Default combo behaviour: lock relative to label + iconPos.x = itemMin.x + textWidth + spacing + LOCK_COMBO_OFFSET_X; + iconPos.y = itemMin.y + (itemSize.y - iconSize) * 0.5f + LOCK_COMBO_OFFSET_Y; + } + + } else if (widgetType == WidgetType::Slider) { + // Slider: same idea, but separate tweakable offsets + iconPos.x = itemMin.x + textWidth + spacing + LOCK_SLIDER_OFFSET_X; + iconPos.y = itemMin.y + (itemSize.y - iconSize) * 0.5f + LOCK_SLIDER_OFFSET_Y; + } + + // Move cursor to where the lock should go + ImGui::SetCursorScreenPos(iconPos); + + // Draw icon-only lock button + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0, 0)); + ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0, 0, 0, 0)); + ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(1, 1, 1, 0.15f)); + ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(1, 1, 1, 0.25f)); + + std::string id = std::string(locked ? ICON_FA_LOCK : ICON_FA_UNLOCK) + "##lock_" + name; + + if (ImGui::Button(id.c_str(), ImVec2(iconSize, iconSize))) { + locked = !locked; + SetLocked(locked); + changed = true; + } + + ImGui::PopStyleColor(3); + ImGui::PopStyleVar(); + + UIWidgets::Tooltip("Lock this option so it isn't changed when randomizing settings."); + + // Restore cursor so layout continues normally + ImGui::SetCursorScreenPos(cursorBackup); + } + ImGui::EndGroup(); return changed; } @@ -192,13 +280,20 @@ Option::Option(size_t key_, std::string name_, std::vector options_ bool Option::RenderCheckbox() { bool changed = false; bool val = static_cast(CVarGetInteger(cvarName.c_str(), defaultOption)); + UIWidgets::CheckboxOptions widgetOptions = static_cast( UIWidgets::CheckboxOptions().Color(THEME_COLOR).Tooltip(description.c_str())); - widgetOptions.disabled = disabled; + + bool locksEnabled = LocksEnabled(); + widgetOptions.disabled = disabled || (locksEnabled && mLocked); + if (UIWidgets::Checkbox(name.c_str(), &val, widgetOptions)) { - CVarSetInteger(cvarName.c_str(), val); - changed = true; - Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); + // Only block changes if locks are enabled AND this option is locked + if (!(locksEnabled && mLocked)) { + CVarSetInteger(cvarName.c_str(), val); + changed = true; + Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); + } } return changed; } @@ -212,17 +307,24 @@ bool Option::RenderCombobox() { changed = true; Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); } + UIWidgets::ComboboxOptions widgetOptions = UIWidgets::ComboboxOptions().Color(THEME_COLOR).Tooltip(description.c_str()); + if (this->GetKey() == RSK_LOGIC_RULES) { widgetOptions = widgetOptions.LabelPosition(UIWidgets::LabelPositions::None) .ComponentAlignment(UIWidgets::ComponentAlignments::Right); } - widgetOptions.disabled = disabled; + + bool locksEnabled = LocksEnabled(); + widgetOptions.disabled = disabled || (locksEnabled && mLocked); + if (UIWidgets::Combobox(name.c_str(), &selected, options, widgetOptions)) { - CVarSetInteger(cvarName.c_str(), static_cast(selected)); - changed = true; - Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); + if (!(locksEnabled && mLocked)) { + CVarSetInteger(cvarName.c_str(), static_cast(selected)); + changed = true; + Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); + } } return changed; } @@ -234,6 +336,7 @@ bool Option::RenderSlider() { val = static_cast(options.size()) - 1; changed = true; } + UIWidgets::IntSliderOptions widgetOptions = UIWidgets::IntSliderOptions() .Color(THEME_COLOR) .Min(0) @@ -241,7 +344,10 @@ bool Option::RenderSlider() { .Tooltip(description.c_str()) .Format(options[val].c_str()) .DefaultValue(defaultOption); - widgetOptions.disabled = disabled; + + bool locksEnabled = LocksEnabled(); + widgetOptions.disabled = disabled || (locksEnabled && mLocked); + if (UIWidgets::SliderInt(name.c_str(), &val, widgetOptions)) { changed = true; } @@ -253,7 +359,8 @@ bool Option::RenderSlider() { val = static_cast(options.size() - 1); changed = true; } - if (changed) { + + if (changed && !(locksEnabled && mLocked)) { CVarSetInteger(cvarName.c_str(), val); Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); } diff --git a/soh/soh/Enhancements/randomizer/option.h b/soh/soh/Enhancements/randomizer/option.h index 55a06ed86d9..f4ae293db5b 100644 --- a/soh/soh/Enhancements/randomizer/option.h +++ b/soh/soh/Enhancements/randomizer/option.h @@ -315,6 +315,14 @@ class Option { uint8_t GetValueFromText(std::string text); void SetContextIndexFromText(std::string text); + bool IsLocked() const { + return mLocked; + } + + void SetLocked(bool locked) { + mLocked = locked; + } + protected: Option(size_t key_, std::string name_, std::vector options_, OptionCategory category_, std::string cvarName_, std::string description_, WidgetType widgetType_, uint8_t defaultOption_, @@ -341,6 +349,7 @@ class Option { bool disabled = false; std::string disabledText; std::unordered_map optionsTextToVar = {}; + bool mLocked = false; }; class LocationOption : public Option { diff --git a/soh/soh/Enhancements/randomizer/option_descriptions.cpp b/soh/soh/Enhancements/randomizer/option_descriptions.cpp index 1f6b71a881a..3844d771143 100644 --- a/soh/soh/Enhancements/randomizer/option_descriptions.cpp +++ b/soh/soh/Enhancements/randomizer/option_descriptions.cpp @@ -46,6 +46,8 @@ void Settings::CreateOptionDescriptions() { "Open - Jabu-Jabu's mouth opens without the need for a fish."; mOptionDescriptions[RSK_LOCK_OVERWORLD_DOORS] = "Add locks to all wooden overworld doors, requiring specific small keys to open them"; + mOptionDescriptions[RSK_RANDOM_LOCKED_DOORS] = "Shuffles which doors are locked within dungeons. *There is NO " + "Logic made for this and therefore could lead to unbeatable seeds*"; mOptionDescriptions[RSK_STARTING_AGE] = "Choose which age Link will start as.\n\n" "Starting as adult means you start with the Master Sword in your inventory.\n" @@ -124,14 +126,14 @@ void Settings::CreateOptionDescriptions() { "set to either MQ or Random here, you will have fewer MQ Dungeons than the number you " "set."; mOptionDescriptions[RSK_TRIFORCE_HUNT] = - "Pieces of the Triforce of Courage have been scattered across the world. Find them all to finish the game!\n\n" - "When the required amount of pieces have been found, the game is saved and Ganon's Boss key is given " + "Ornaments have been scattered across the world. Find them all to finish the game!\n\n" + "When the required amount of ornaments have been found, the game is saved and Ganon's Boss key is given " "to you when you load back into the game if you desire to beat Ganon afterwards.\n\n" "Keep in mind Ganon might not be logically beatable when \"All Locations Reachable\" is turned off."; - mOptionDescriptions[RSK_TRIFORCE_HUNT_PIECES_TOTAL] = - "The amount of Triforce pieces that will be placed in the world. " - "Keep in mind seed generation can fail if more pieces are placed than there are junk items in the item pool."; - mOptionDescriptions[RSK_TRIFORCE_HUNT_PIECES_REQUIRED] = "The amount of Triforce pieces required to win the game."; + mOptionDescriptions[RSK_TRIFORCE_HUNT_PIECES_TOTAL] = "The amount of Ornaments that will be placed in the world. " + "Keep in mind seed generation can fail if more ornaments are " + "placed than there are junk items in the item pool."; + mOptionDescriptions[RSK_TRIFORCE_HUNT_PIECES_REQUIRED] = "The amount of Ornaments required to win the game."; mOptionDescriptions[RSK_SHUFFLE_DUNGEON_ENTRANCES] = "Shuffle the pool of dungeon entrances, including Bottom of the Well, Ice Cavern and Gerudo Training Ground.\n" "\n" @@ -778,5 +780,18 @@ void Settings::CreateOptionDescriptions() { "Shuffles 8 boss souls (one for each blue warp dungeon). A boss will not appear until you collect its " "respective soul." "\n\"On + Ganon\" will also hide Ganon and Ganondorf behind a boss soul."; + + mOptionDescriptions[RSK_SHUFFLE_ISG] = + "Shuffles the ability to use the Infinite Sword Glitch (ISG) into the item pool."; + mOptionDescriptions[RSK_SHUFFLE_OI] = "Shuffles the ability to use the glitch Ocarina Items into the item pool."; + mOptionDescriptions[RSK_SHUFFLE_QPA] = + "Shuffles the ability to use Quick Putaway Glitched Damage into the item pool."; + mOptionDescriptions[RSK_SHUFFLE_HESS] = + "Shuffles the ability to perform an Extended Superslide into the item pool."; + mOptionDescriptions[RSK_SHUFFLE_SUPERSLIDE] = "Shuffles the ability to Superslide into the item pool."; + mOptionDescriptions[RSK_SHUFFLE_HOVER] = "Shuffles the ability to Hover into the item pool."; + mOptionDescriptions[RSK_SHUFFLE_EQUIP_SWAP] = "Shuffles the ability to Equip Swap into the item pool."; + mOptionDescriptions[RSK_SHUFFLE_GROUND_JUMP] = "Shuffles the ability to Ground Jump into the item pool."; + mOptionDescriptions[RSK_SHUFFLE_WEIRDSHOT] = "Shuffles the ability to Weirdshot into the item pool."; } } // namespace Rando diff --git a/soh/soh/Enhancements/randomizer/randomizer.cpp b/soh/soh/Enhancements/randomizer/randomizer.cpp index de17327f19b..cfd17b01f15 100644 --- a/soh/soh/Enhancements/randomizer/randomizer.cpp +++ b/soh/soh/Enhancements/randomizer/randomizer.cpp @@ -40,6 +40,7 @@ #include "soh/util.h" #include "fishsanity.h" #include "randomizerTypes.h" +#include "soh/Network/Archipelago/Archipelago.h" #include "soh/Notification/Notification.h" extern std::map rcAreaNames; @@ -65,6 +66,8 @@ const std::string Randomizer::triforcePieceMessageTableID = "RandomizerTriforceP const std::string Randomizer::NaviRandoMessageTableID = "RandomizerNavi"; const std::string Randomizer::IceTrapRandoMessageTableID = "RandomizerIceTrap"; const std::string Randomizer::randoMiscHintsTableID = "RandomizerMiscHints"; +const std::string Randomizer::archipelagoItemsTableID = "ÁrchipelagoItems"; +const std::string Randomizer::christmasTreeMessageTableID = "RandomizerChristmasTree"; static const char* englishRupeeNames[188] = { "[P]", @@ -3640,19 +3643,24 @@ ShopItemIdentity Randomizer::IdentifyShopItem(s32 sceneNum, u8 slotIndex) { (sceneNum == SCENE_BAZAAR && gSaveContext.entranceIndex == ENTR_BAZAAR_0) ? SCENE_TEST01 : sceneNum, slotIndex - 1); - if (location->GetRandomizerCheck() != RC_UNKNOWN_CHECK) { - shopItemIdentity.randomizerInf = rcToRandomizerInf[location->GetRandomizerCheck()]; - shopItemIdentity.randomizerCheck = location->GetRandomizerCheck(); - shopItemIdentity.ogItemId = (GetItemID)Rando::StaticData::RetrieveItem(location->GetVanillaItem()).GetItemID(); - - RandomizerGet randoGet = - Rando::Context::GetInstance()->GetItemLocation(shopItemIdentity.randomizerCheck)->GetPlacedRandomizerGet(); - if (randomizerGetToEnGirlShopItem.find(randoGet) != randomizerGetToEnGirlShopItem.end()) { - shopItemIdentity.enGirlAShopItem = randomizerGetToEnGirlShopItem[randoGet]; - } + RandomizerCheck randoCheck = location->GetRandomizerCheck(); + + if (randoCheck != RC_UNKNOWN_CHECK) { + RandomizerGet randoGet = Rando::Context::GetInstance()->GetItemLocation(randoCheck)->GetPlacedRandomizerGet(); - shopItemIdentity.itemPrice = - OTRGlobals::Instance->gRandoContext->GetItemLocation(shopItemIdentity.randomizerCheck)->GetPrice(); + if (randoGet != RG_NONE) { + shopItemIdentity.randomizerInf = rcToRandomizerInf[randoCheck]; + shopItemIdentity.randomizerCheck = randoCheck; + shopItemIdentity.ogItemId = + (GetItemID)Rando::StaticData::RetrieveItem(location->GetVanillaItem()).GetItemID(); + + if (randomizerGetToEnGirlShopItem.find(randoGet) != randomizerGetToEnGirlShopItem.end()) { + shopItemIdentity.enGirlAShopItem = randomizerGetToEnGirlShopItem[randoGet]; + } + + shopItemIdentity.itemPrice = + OTRGlobals::Instance->gRandoContext->GetItemLocation(shopItemIdentity.randomizerCheck)->GetPrice(); + } } return shopItemIdentity; @@ -4012,6 +4020,49 @@ void RandomizerSettingsWindow::DrawElement() { GenerateRandomizer(CVarGetInteger(CVAR_RANDOMIZER_SETTING("ManualSeedEntry"), 0) ? seedString : ""); } + ImGui::SameLine(); + UIWidgets::ButtonOptions randomizeOptions = UIWidgets::ButtonOptions().Size(ImVec2(250.f, 0.f)).Color(THEME_COLOR); + randomizeOptions.Disabled(disableEditingRandoSettings); + randomizeOptions.Tooltip("Randomizes all randomizer settings to random valid values (excludes tricks)."); + if (UIWidgets::Button("Randomize All Settings", randomizeOptions)) { + mSettings->RandomizeAllSettings(); + } + + ImGui::SameLine(); + +#define CVAR_RANDO_LOCKS_ENABLED CVAR_SETTING("RandoLocksEnabled") + + bool locksEnabled = CVarGetInteger(CVAR_RANDO_LOCKS_ENABLED, 0) != 0; + + UIWidgets::CheckboxOptions lockToggleOpts = + UIWidgets::CheckboxOptions().Color(THEME_COLOR).Tooltip("Enable per-option locking of randomizer settings."); + + if (UIWidgets::Checkbox("Enable Locks", &locksEnabled, lockToggleOpts)) { + CVarSetInteger(CVAR_RANDO_LOCKS_ENABLED, locksEnabled ? 1 : 0); + Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); + } + + if (locksEnabled) { + ImGui::SameLine(); + + UIWidgets::ButtonOptions lockAllOptions = + UIWidgets::ButtonOptions().Size(ImVec2(160.f, 0.f)).Color(THEME_COLOR); + lockAllOptions.Disabled(disableEditingRandoSettings); + lockAllOptions.Tooltip("Lock every randomizer setting so randomization cannot modify them."); + if (UIWidgets::Button("Lock All", lockAllOptions)) { + mSettings->LockAllOptions(); + } + + ImGui::SameLine(); + UIWidgets::ButtonOptions unlockAllOptions = + UIWidgets::ButtonOptions().Size(ImVec2(160.f, 0.f)).Color(THEME_COLOR); + unlockAllOptions.Disabled(disableEditingRandoSettings); + unlockAllOptions.Tooltip("Unlock every setting so randomization may modify them again."); + if (UIWidgets::Button("Unlock All", unlockAllOptions)) { + mSettings->UnlockAllOptions(); + } + } + ImGui::SameLine(); if (!CVarGetInteger(CVAR_RANDOMIZER_SETTING("DontGenerateSpoiler"), 0)) { std::string spoilerfilepath = CVarGetString(CVAR_GENERAL("SpoilerLog"), ""); @@ -4717,6 +4768,12 @@ CustomMessage Randomizer::GetMerchantMessage(RandomizerCheck rc, TextIDs textId, } else if (shopItemGet == RG_ICE_TRAP) { shopItemGet = ctx->overrides[rc].LooksLike(); shopItemName = CustomMessage(ctx->overrides[rc].GetTrickName()); + } else if (shopItemGet == RG_ARCHIPELAGO_ITEM_PROGRESSIVE || shopItemGet == RG_ARCHIPELAGO_ITEM_USEFUL || + shopItemGet == RG_ARCHIPELAGO_ITEM_JUNK) { + auto shopItem = Rando::StaticData::RetrieveItem(shopItemGet); + std::string apItemName = std::string(gSaveContext.ship.quest.data.archipelago.locations[rc].itemName) + " (" + + std::string(gSaveContext.ship.quest.data.archipelago.locations[rc].playerName) + ")"; + shopItemName = { Text(apItemName) }; } else { auto shopItem = Rando::StaticData::RetrieveItem(shopItemGet); shopItemName = { shopItem.GetName() }; @@ -4833,39 +4890,82 @@ CustomMessage Randomizer::GetRupeeMessage(u16 rupeeTextId) { return messageEntry; } +// Normal Triforce text +static const CustomMessage kTriforcePieceMessages_Normal[NUM_TRIFORCE_PIECE_MESSAGES] = { + { "You found a %yTriforce Piece%w!&%g[[current]]%w down, %c[[remaining]]%w to go. It's a start!", + "Ein %yTriforce-Splitter%w! Du hast&%g[[current]]%w von %c[[required]]%w gefunden. Es ist ein&Anfang!", + "Vous trouvez un %yFragment de la&Triforce%w! Vous en avez %g[[current]]%w, il en&reste %c[[remaining]]%w à " + "trouver. C'est un début!" }, + + { "You found a %yTriforce Piece%w!&%g[[current]]%w down, %c[[remaining]]%w to go. Progress!", + "Ein %yTriforce-Splitter%w! Du hast&%g[[current]]%w von %c[[required]]%w gefunden. Es geht voran!", + "Vous trouvez un %yFragment de la&Triforce%w! Vous en avez %g[[current]]%w, il en&reste %c[[remaining]]%w à " + "trouver. Ça avance!" }, + + { "You found a %yTriforce Piece%w!&%g[[current]]%w down, %c[[remaining]]%w to go. Over half-way&there!", + "Ein %yTriforce-Splitter%w! Du hast&schon %g[[current]]%w von %c[[required]]%w gefunden. Schon&über die Hälfte!", + "Vous trouvez un %yFragment de la&Triforce%w! Vous en avez %g[[current]]%w, il en&reste %c[[remaining]]%w à " + "trouver. Il en reste un&peu moins que la moitié!" }, + + { "You found a %yTriforce Piece%w!&%g[[current]]%w down, %c[[remaining]]%w to go. Almost done!", + "Ein %yTriforce-Splitter%w! Du hast&schon %g[[current]]%w von %c[[required]]%w gefunden. Fast&geschafft!", + "Vous trouvez un %yFragment de la&Triforce%w! Vous en avez %g[[current]]%w, il en&reste %c[[remaining]]%w à " + "trouver. C'est presque&terminé!" }, + + { "You completed the %yTriforce of&Courage%w! %gGG%w!", + "Das %yTriforce des Mutes%w! Du hast&alle Splitter gefunden. %gGut gemacht%w!", + "Vous avez complété la %yTriforce&du Courage%w! %gFélicitations%w!" }, + + { "You found a spare %yTriforce Piece%w!&You only needed %c[[required]]%w, but you have %g[[current]]%w!", + "Ein übriger %yTriforce-Splitter%w! Du&hast nun %g[[current]]%w von %c[[required]]%w nötigen gefunden.", + "Vous avez trouvé un %yFragment de&Triforce%w en plus! Vous n'aviez besoin&que de %c[[required]]%w, mais vous en " + "avez %g[[current]]%w en&tout!" }, +}; + +// Holiday / Ornament text +static const CustomMessage kTriforcePieceMessages_Holiday[NUM_TRIFORCE_PIECE_MESSAGES] = { + { "You found a %yChristmas Ornament%w!&%g[[current]]%w down, %c[[remaining]]%w to go. It's a start!", + "Ein %yChristmas Ornament%w! Du hast&%g[[current]]%w von %c[[required]]%w gefunden. Es ist ein&Anfang!", + "Vous trouvez un %yChristmas Ornament%w! Vous en avez %g[[current]]%w, il en&reste %c[[remaining]]%w à " + "trouver. C'est un début!" }, + + { "You found a %yChristmas Ornament%w!&%g[[current]]%w down, %c[[remaining]]%w to go. Progress!", + "Ein %yChristmas Ornament%w! Du hast&%g[[current]]%w von %c[[required]]%w gefunden. Es geht voran!", + "Vous trouvez un %yChristmas Ornament%w! Vous en avez %g[[current]]%w, il en&reste %c[[remaining]]%w à " + "trouver. Ça avance!" }, + + { "You found a %yChristmas Ornament%w!&%g[[current]]%w down, %c[[remaining]]%w to go. Over half-way&there!", + "Ein %yChristmas Ornament%w! Du hast&schon %g[[current]]%w von %c[[required]]%w gefunden. Schon&über die " + "Hälfte!", + "Vous trouvez un %yChristmas Ornament%w! Vous en avez %g[[current]]%w, il en&reste %c[[remaining]]%w à " + "trouver. Il en reste un&peu moins que la moitié!" }, + + { "You found a %yChristmas Ornament%w!&%g[[current]]%w down, %c[[remaining]]%w to go. Almost done!", + "Ein %yChristmas Ornament%w! Du hast&schon %g[[current]]%w von %c[[required]]%w gefunden. Fast&geschafft!", + "Vous trouvez un %yChristmas Ornament%w! Vous en avez %g[[current]]%w, il en&reste %c[[remaining]]%w à " + "trouver. C'est presque&terminé!" }, + + { "You found all of the %yChristmas&Ornaments%w! Visit the %gChristmas&tree%w in Kakariko Village!", + "Das %yTriforce des Mutes%w! Du hast&alle Splitter gefunden. %gGut gemacht%w!", + "Vous avez complété la %yTriforce&du Courage%w! %gFélicitations%w!" }, + + { "You found a spare %yChristmas Ornament%w!&You only needed %c[[required]]%w, but you have %g[[current]]%w!", + "Noch ein %yChristmas Ornament%w! Du&brauchtest nur %c[[required]]%w, hast jetzt aber %g[[current]]%w!", + "Vous avez trouvé un %yFragment de&Triforce%w en plus! Vous n'aviez besoin&que de %c[[required]]%w, mais " + "vous en avez %g[[current]]%w en&tout!" }, +}; + void CreateTriforcePieceMessages() { - CustomMessage TriforcePieceMessages[NUM_TRIFORCE_PIECE_MESSAGES] = { - - { "You found a %yTriforce Piece%w!&%g[[current]]%w down, %c[[remaining]]%w to go. It's a start!", - "Ein %yTriforce-Splitter%w! Du hast&%g[[current]]%w von %c[[required]]%w gefunden. Es ist ein&Anfang!", - "Vous trouvez un %yFragment de la&Triforce%w! Vous en avez %g[[current]]%w, il en&reste %c[[remaining]]%w à " - "trouver. C'est un début!" }, - - { "You found a %yTriforce Piece%w!&%g[[current]]%w down, %c[[remaining]]%w to go. Progress!", - "Ein %yTriforce-Splitter%w! Du hast&%g[[current]]%w von %c[[required]]%w gefunden. Es geht voran!", - "Vous trouvez un %yFragment de la&Triforce%w! Vous en avez %g[[current]]%w, il en&reste %c[[remaining]]%w à " - "trouver. Ça avance!" }, - - { "You found a %yTriforce Piece%w!&%g[[current]]%w down, %c[[remaining]]%w to go. Over half-way&there!", - "Ein %yTriforce-Splitter%w! Du hast&schon %g[[current]]%w von %c[[required]]%w gefunden. Schon&über die " - "Hälfte!", - "Vous trouvez un %yFragment de la&Triforce%w! Vous en avez %g[[current]]%w, il en&reste %c[[remaining]]%w à " - "trouver. Il en reste un&peu moins que la moitié!" }, - - { "You found a %yTriforce Piece%w!&%g[[current]]%w down, %c[[remaining]]%w to go. Almost done!", - "Ein %yTriforce-Splitter%w! Du hast&schon %g[[current]]%w von %c[[required]]%w gefunden. Fast&geschafft!", - "Vous trouvez un %yFragment de la&Triforce%w! Vous en avez %g[[current]]%w, il en&reste %c[[remaining]]%w à " - "trouver. C'est presque&terminé!" }, - - { "You completed the %yTriforce of&Courage%w! %gGG%w!", - "Das %yTriforce des Mutes%w! Du hast&alle Splitter gefunden. %gGut gemacht%w!", - "Vous avez complété la %yTriforce&du Courage%w! %gFélicitations%w!" }, - - { "You found a spare %yTriforce Piece%w!&You only needed %c[[required]]%w, but you have %g[[current]]%w!", - "Noch ein %yTriforce-Splitter%w! Du&brauchtest nur %c[[required]]%w, hast jetzt aber %g[[current]]%w!", - "Vous avez trouvé un %yFragment de&Triforce%w en plus! Vous n'aviez besoin&que de %c[[required]]%w, mais " - "vous en avez %g[[current]]%w en&tout!" }, - }; + // Pick which table to use based on the HolidayPieces CVar + const CustomMessage* src = CVarGetInteger("gHoliday.Visual.HolidayPieces", 0) ? kTriforcePieceMessages_Holiday + : kTriforcePieceMessages_Normal; + + CustomMessage TriforcePieceMessages[NUM_TRIFORCE_PIECE_MESSAGES]; + + for (int i = 0; i < NUM_TRIFORCE_PIECE_MESSAGES; ++i) { + TriforcePieceMessages[i] = src[i]; + } + CustomMessageManager* customMessageManager = CustomMessageManager::Instance; customMessageManager->AddCustomMessageTable(Randomizer::triforcePieceMessageTableID); for (unsigned int i = 0; i <= (NUM_TRIFORCE_PIECE_MESSAGES - 1); i++) { @@ -4904,6 +5004,44 @@ CustomMessage Randomizer::GetTriforcePieceMessage() { return messageEntry; } +void CreateChristmasTreeMessages() { + CustomMessage ChristmasTreeMessages[2] = { + + { "The %yChristmas tree%w seems to be&missing some of %gits magic%w... Find all&ornaments to save " + "%rChristmas%w!", + "The %yChristmas tree%w seems to be&missing some of %gits magic%w... Find all&ornaments to save " + "%rChristmas%w!", + "The %yChristmas tree%w seems to be&missing some of %gits magic%w... Find all&ornaments to save " + "%rChristmas%w!" }, + + { "The tree's magic has been fully&restored. %gMerry %rChristmas%w!", + "The tree's magic has been fully&restored. %gMerry %rChristmas%w!", + "The tree's magic has been fully&restored. %gMerry %rChristmas%w!" } + }; + CustomMessageManager* customMessageManager = CustomMessageManager::Instance; + customMessageManager->AddCustomMessageTable(Randomizer::christmasTreeMessageTableID); + for (unsigned int i = 0; i <= 1; i++) { + customMessageManager->CreateMessage(Randomizer::christmasTreeMessageTableID, i, ChristmasTreeMessages[i]); + } +} + +CustomMessage Randomizer::GetChristmasTreeMessage() { + // Item is only given after the textbox, so reflect that inside the textbox. + uint8_t current = gSaveContext.ship.quest.data.randomizer.triforcePiecesCollected; + uint8_t required = OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_TRIFORCE_HUNT_PIECES_REQUIRED); + uint8_t messageIndex; + + if (current < required) { + messageIndex = 0; + } else { + messageIndex = 1; + } + + CustomMessage messageEntry = + CustomMessageManager::Instance->RetrieveMessage(Randomizer::christmasTreeMessageTableID, messageIndex); + return messageEntry; +} + void CreateNaviRandoMessages() { CustomMessage NaviMessages[NUM_NAVI_MESSAGES] = { @@ -5512,11 +5650,42 @@ CustomMessage Randomizer::GetGoronMessage(u16 index) { return messageEntry; } +void CreateArchipelagoItemMessage() { + CustomMessageManager* customMessageManager = CustomMessageManager::Instance; + customMessageManager->AddCustomMessageTable(Randomizer::archipelagoItemsTableID); + customMessageManager->CreateMessage(Randomizer::archipelagoItemsTableID, 0, + CustomMessage("You found [[apcolor]][[apitem]]%w for %r[[applayer]]%w!", + "You found [[apcolor]][[apitem]]%w for %r[[applayer]]%w!", + "You found [[apcolor]][[apitem]]%w for %r[[applayer]]%w!")); +} + +CustomMessage Randomizer::GetArchipelagoItemMessage(int16_t randomizerGet, uint32_t randomizerCheck) { + CustomMessage messageEntry = + CustomMessageManager::Instance->RetrieveMessage(Randomizer::archipelagoItemsTableID, 0); + + std::string itemColor = ""; + if (randomizerGet == RG_ARCHIPELAGO_ITEM_PROGRESSIVE) { + itemColor = "%p"; + } else if (randomizerGet == RG_ARCHIPELAGO_ITEM_USEFUL) { + itemColor = "%b"; + } else { + itemColor = "%c"; + } + + messageEntry.Replace("[[apcolor]]", itemColor); + messageEntry.Replace("[[apitem]]", + std::string(gSaveContext.ship.quest.data.archipelago.locations[randomizerCheck].itemName)); + messageEntry.Replace("[[applayer]]", + std::string(gSaveContext.ship.quest.data.archipelago.locations[randomizerCheck].playerName)); + messageEntry.AutoFormat(); + return messageEntry; +} + void Randomizer::CreateCustomMessages() { // RANDTODO: Translate into french and german and replace GIMESSAGE_UNTRANSLATED // with GIMESSAGE(getItemID, itemID, english, german, french). - const std::array getItemMessages = { { - GIMESSAGE(RG_GREG_RUPEE, ITEM_MASK_GORON, "You found %gGreg%w!", "%gGreg%w! Du hast ihn&wirklich gefunden!", + const std::array getItemMessages = { { + GIMESSAGE(RG_GREG_RUPEE, ITEM_MASK_GORON, "You found %gGreg%w!", "%gGreg%w! Du hast ihn wirklich gefunden!", "Félicitation! Vous avez trouvé %gGreg%w!"), GIMESSAGE(RG_MASTER_SWORD, ITEM_SWORD_MASTER, "You found the %gMaster Sword%w!", "Du erhältst das %gMaster-Schwert%w!", "Vous obtenez %gl'Épée de Légende%w!"), @@ -5839,6 +6008,25 @@ void Randomizer::CreateCustomMessages() { GIMESSAGE(RG_BRONZE_SCALE, ITEM_SCALE_SILVER, "You got the %rBronze Scale%w!&The power of buoyancy is yours!", "Du hast die %rBronzene Schuppe%w&erhalten! Die Fähigkeit zu&Schwimmen ist nun dein!", "Vous obtenez l'%rÉcaille de Bronze%w!&Le pouvoir de la flottabilité est&à vous!"), + + GIMESSAGE(RG_ABILITY_ISG, ITEM_SWORD_MASTER, "You got %rISG%w!", "Du hast %rISG%w&erhalten!", + "Vous obtenez %rISG%w!"), + GIMESSAGE(RG_ABILITY_OI, ITEM_OCARINA_TIME, "You got %rOI%w!", "Du hast %rOI%w&erhalten!", + "Vous obtenez %rOI%w!"), + GIMESSAGE(RG_ABILITY_QPA, ITEM_BLUE_FIRE, "You got %rQPA%w!", "Du hast %rQPA%w&erhalten!", + "Vous obtenez %rQPA%w!"), + GIMESSAGE(RG_ABILITY_HESS, ITEM_BOOTS_HOVER, "You got %rHESS%w!", "Du hast %rHESS%w&erhalten!", + "Vous obtenez %rHESS%w!"), + GIMESSAGE(RG_ABILITY_SUPERSLIDE, ITEM_BOOTS_HOVER, "You got %rSuperslide%w!", + "Du hast %rSuperslide%w&erhalten!", "Vous obtenez %rSuperslide%w!"), + GIMESSAGE(RG_ABILITY_HOVER, ITEM_BOOTS_HOVER, "You got %rHover%w!", "Du hast %rHover%w&erhalten!", + "Vous obtenez %rHover%w!"), + GIMESSAGE(RG_ABILITY_EQUIP_SWAP, ITEM_DINS_FIRE, "You got %rEquip Swap%w!", "Du hast %rEquip Swap%w&erhalten!", + "Vous obtenez %rEquip Swap%w!"), + GIMESSAGE(RG_ABILITY_GROUND_JUMP, ITEM_SHIELD_HYLIAN, "You got %rGround Jump%w!", + "Du hast %rGround Jump%w&erhalten!", "Vous obtenez %rGround Jump%w!"), + GIMESSAGE(RG_ABILITY_WEIRDSHOT, ITEM_HOOKSHOT, "You got %rWeirdshot%w!", "Du hast %rWeirdshot%w&erhalten!", + "Vous obtenez %rWeirdshot%w!"), GIMESSAGE(RG_FISHING_POLE, ITEM_FISHING_POLE, "You found a lost %rFishing Pole%w!&Time to hit the pond!", "Du hast eine verlorene %rAngelrute%w&gefunden!&Zeit, im Teich&zu angeln!", "Vous obtenez une %rCanne à pêche%w&perdue!&Il est temps d'aller à %gl'étang%w!"), @@ -5885,6 +6073,7 @@ void Randomizer::CreateCustomMessages() { CreateTriforcePieceMessages(); CreateNaviRandoMessages(); CreateFireTempleGoronMessages(); + CreateArchipelagoItemMessage(); } class ExtendedVanillaTableInvalidItemIdException : public std::exception { @@ -6031,6 +6220,15 @@ std::map randomizerGetToRandInf = { { RG_BONGO_BONGO_SOUL, RAND_INF_BONGO_BONGO_SOUL }, { RG_TWINROVA_SOUL, RAND_INF_TWINROVA_SOUL }, { RG_GANON_SOUL, RAND_INF_GANON_SOUL }, + { RG_ABILITY_ISG, RAND_INF_CAN_ISG }, + { RG_ABILITY_OI, RAND_INF_CAN_OI }, + { RG_ABILITY_QPA, RAND_INF_CAN_QPA }, + { RG_ABILITY_HESS, RAND_INF_CAN_HESS }, + { RG_ABILITY_SUPERSLIDE, RAND_INF_CAN_SUPERSLIDE }, + { RG_ABILITY_HOVER, RAND_INF_CAN_HOVER }, + { RG_ABILITY_EQUIP_SWAP, RAND_INF_CAN_EQUIP_SWAP }, + { RG_ABILITY_GROUND_JUMP, RAND_INF_CAN_GROUND_JUMP }, + { RG_ABILITY_WEIRDSHOT, RAND_INF_CAN_WEIRDSHOT }, }; extern "C" u16 Randomizer_Item_Give(PlayState* play, GetItemEntry giEntry) { @@ -6053,6 +6251,12 @@ extern "C" u16 Randomizer_Item_Give(PlayState* play, GetItemEntry giEntry) { return Return_Item_Entry(giEntry, RG_NONE); } + // If it's an archipelago item, don't give anything + if (item == RG_ARCHIPELAGO_ITEM_USEFUL || item == RG_ARCHIPELAGO_ITEM_JUNK || + item == RG_ARCHIPELAGO_ITEM_PROGRESSIVE) { + return Return_Item_Entry(giEntry, RG_NONE); + } + // bottle items if (item >= RG_BOTTLE_WITH_RED_POTION && item <= RG_BOTTLE_WITH_BIG_POE) { for (u16 i = 0; i < 4; i++) { @@ -6291,6 +6495,7 @@ extern "C" u16 Randomizer_Item_Give(PlayState* play, GetItemEntry giEntry) { gSaveContext.ship.stats.itemTimestamp[TIMESTAMP_TRIFORCE_COMPLETED] = static_cast(GAMEPLAYSTAT_TOTAL_TIME); gSaveContext.ship.stats.gameComplete = 1; + ArchipelagoClient::GetInstance().SendGameWon(); Flags_SetRandomizerInf(RAND_INF_GRANT_GANONS_BOSSKEY); Play_PerformSave(play); Notification::Emit({ diff --git a/soh/soh/Enhancements/randomizer/randomizer.h b/soh/soh/Enhancements/randomizer/randomizer.h index 54c85307b6f..228ba1a8ff6 100644 --- a/soh/soh/Enhancements/randomizer/randomizer.h +++ b/soh/soh/Enhancements/randomizer/randomizer.h @@ -38,6 +38,8 @@ class Randomizer { static const std::string NaviRandoMessageTableID; static const std::string IceTrapRandoMessageTableID; static const std::string randoMiscHintsTableID; + static const std::string archipelagoItemsTableID; + static const std::string christmasTreeMessageTableID; static Sprite* GetSeedTexture(uint8_t index); bool SpoilerFileExists(const char* spoilerFileName); @@ -75,6 +77,8 @@ class Randomizer { static CustomMessage GetRupeeMessage(u16 rupeeTextId); static CustomMessage GetIceTrapMessage(); static CustomMessage GetTriforcePieceMessage(); + static CustomMessage GetArchipelagoItemMessage(int16_t randomizerGet, uint32_t randomizerCheck); + static CustomMessage GetChristmasTreeMessage(); }; #ifdef __cplusplus diff --git a/soh/soh/Enhancements/randomizer/randomizerTypes.h b/soh/soh/Enhancements/randomizer/randomizerTypes.h index 02bffe53ed8..cf08470ba15 100644 --- a/soh/soh/Enhancements/randomizer/randomizerTypes.h +++ b/soh/soh/Enhancements/randomizer/randomizerTypes.h @@ -3776,6 +3776,7 @@ typedef enum { RC_DEKU_TREE_QUEEN_GOHMA_GRASS_8, // End Grass + RC_ARCHIPELAGO_RECEIVED_ITEM, RC_MAX } RandomizerCheck; @@ -4333,6 +4334,21 @@ typedef enum { RG_BACK_TOWER_KEY, RG_HYLIA_LAB_KEY, RG_FISHING_HOLE_KEY, + RG_ARCHIPELAGO_ITEM_USEFUL, + RG_ARCHIPELAGO_ITEM_JUNK, + RG_ARCHIPELAGO_ITEM_PROGRESSIVE, + + // Glitch abilities + RG_ABILITY_ISG, + RG_ABILITY_OI, + RG_ABILITY_QPA, + RG_ABILITY_HESS, + RG_ABILITY_SUPERSLIDE, + RG_ABILITY_HOVER, + RG_ABILITY_EQUIP_SWAP, + RG_ABILITY_GROUND_JUMP, + RG_ABILITY_WEIRDSHOT, + // Logic Only RG_DISTANT_SCARECROW, RG_STICKS, @@ -5937,6 +5953,17 @@ typedef enum { RHT_DODONGOS_CAVERN_GRASS, RHT_BOTTOM_OF_THE_WELL_GRASS, RHT_JABU_JABUS_BELLY_GRASS, + + // Glitch Abilities + RHT_ABILITY_ISG, + RHT_ABILITY_OI, + RHT_ABILITY_QPA, + RHT_ABILITY_HESS, + RHT_ABILITY_SUPERSLIDE, + RHT_ABILITY_HOVER, + RHT_ABILITY_EQUIP_SWAP, + RHT_ABILITY_GROUND_JUMP, + RHT_ABILITY_WEIRDSHOT, // MAX RHT_MAX, } RandomizerHintTextKey; @@ -6238,6 +6265,17 @@ typedef enum { RSK_SHUFFLE_SONG_FAIRIES, RSK_LOCK_OVERWORLD_DOORS, RSK_SHUFFLE_GRASS, + RSK_RANDOM_LOCKED_DOORS, + // Glitch shuffles + RSK_SHUFFLE_ISG, + RSK_SHUFFLE_OI, + RSK_SHUFFLE_QPA, + RSK_SHUFFLE_HESS, + RSK_SHUFFLE_SUPERSLIDE, + RSK_SHUFFLE_HOVER, + RSK_SHUFFLE_EQUIP_SWAP, + RSK_SHUFFLE_GROUND_JUMP, + RSK_SHUFFLE_WEIRDSHOT, RSK_MAX } RandomizerSettingKey; diff --git a/soh/soh/Enhancements/randomizer/randomizer_check_tracker.cpp b/soh/soh/Enhancements/randomizer/randomizer_check_tracker.cpp index 9363ace39b1..c651fcd0d1d 100644 --- a/soh/soh/Enhancements/randomizer/randomizer_check_tracker.cpp +++ b/soh/soh/Enhancements/randomizer/randomizer_check_tracker.cpp @@ -269,6 +269,8 @@ std::vector buttons = { BTN_A, BTN_B, BTN_CUP, BTN_CDOWN, BTN_CLEFT, BTN_Z, BTN_R, BTN_START, BTN_DUP, BTN_DDOWN, BTN_DLEFT, BTN_DRIGHT }; static ImGuiTextFilter checkSearch; static bool recalculateAvailable = false; +static RandomizerRegion availableChecksStartingRegion = RR_ROOT; +static int16_t previousEntrance = 0; std::array filterAreasHidden = { 0 }; std::array filterChecksHidden = { 0 }; @@ -609,9 +611,7 @@ void CheckTrackerLoadGame(int32_t fileNum) { RegionTable_Init(); - if (Rando::Context::GetInstance()->GetOption(RSK_SHUFFLE_ENTRANCES).Get()) { - Rando::Context::GetInstance()->GetEntranceShuffler()->ApplyEntranceOverrides(); - } + Rando::Context::GetInstance()->GetEntranceShuffler()->ApplyEntranceOverrides(); recalculateAvailable = true; } @@ -953,6 +953,8 @@ void SetAreaSpoiled(RandomizerCheckArea rcArea) { SaveManager::Instance->SaveSection(gSaveContext.fileNum, sectionId, true); } +void InternalRecalculateAvailableChecks(RandomizerRegion startingRegion); + void CheckTrackerWindow::DrawElement() { Color_Background = CVarGetColor(CVAR_TRACKER_CHECK("BgColor.Value"), Color_Bg_Default); Color_Area_Incomplete_Main = CVarGetColor(CVAR_TRACKER_CHECK("AreaIncomplete.MainColor.Value"), Color_Main_Default); @@ -1026,9 +1028,15 @@ void CheckTrackerWindow::DrawElement() { return; } + if (gPlayState->nextEntranceIndex != previousEntrance) { + previousEntrance = gPlayState->nextEntranceIndex; + recalculateAvailable = true; + } + if (recalculateAvailable) { recalculateAvailable = false; - RecalculateAvailableChecks(); + InternalRecalculateAvailableChecks(availableChecksStartingRegion); + availableChecksStartingRegion = RR_ROOT; } SceneID sceneId = SCENE_ID_MAX; @@ -1088,15 +1096,37 @@ void CheckTrackerWindow::DrawElement() { } UIWidgets::PushStyleCombobox(THEME_COLOR); if (CVarGetInteger(CVAR_TRACKER_CHECK("SearchInputVisible"), 1)) { - if (checkSearch.Draw("", ImGui::GetContentRegionAvail().x - 6)) { + + const ImGuiStyle& style = ImGui::GetStyle(); + const float clearTextW = ImGui::CalcTextSize("Clear").x; + const float clearBtnW = clearTextW + (style.FramePadding.x * 2.0f); + const float spacingW = style.ItemSpacing.x; + + const float inputW = ImGui::GetContentRegionAvail().x - (clearBtnW + spacingW); + const float finalInputW = (inputW > 0.0f) ? inputW : 0.0f; + + if (checkSearch.Draw("", finalInputW)) { + UpdateFilters(); + } + + const ImVec2 inputMin = ImGui::GetItemRectMin(); + const ImVec2 inputMax = ImGui::GetItemRectMax(); + + ImGui::SameLine(0.0f, spacingW); + + if (UIWidgets::Button("Clear", UIWidgets::ButtonOptions({ { .tooltip = "Clear the search field" } }) + .Color(THEME_COLOR) + .Size(UIWidgets::Sizes::Inline))) { + checkSearch.Clear(); UpdateFilters(); + doAreaScroll = true; } - std::string checkSearchText = ""; - checkSearchText = checkSearch.InputBuf; - checkSearchText.erase(std::remove(checkSearchText.begin(), checkSearchText.end(), ' '), checkSearchText.end()); - if (checkSearchText.length() < 1) { - ImGui::SameLine(20.0f); - ImGui::TextColored(ImVec4(1.0f, 1.0f, 1.0f, 0.4f), "Search..."); + + // --- Placeholder overlay (drawn inside the INPUT rect, not the button rect) --- + if (checkSearch.InputBuf[0] == '\0') { + ImGui::GetWindowDrawList()->AddText( + ImVec2(inputMin.x + style.FramePadding.x, inputMin.y + style.FramePadding.y), + ImGui::GetColorU32(ImVec4(1, 1, 1, 0.4f)), "Search..."); } } UIWidgets::PopStyleCombobox(); @@ -2054,7 +2084,7 @@ void ImGuiDrawTwoColorPickerSection(const char* text, const char* cvarMainName, UIWidgets::PopStyleCombobox(); } -void RecalculateAvailableChecks(RandomizerRegion startingRegion /* = RR_ROOT */) { +void InternalRecalculateAvailableChecks(RandomizerRegion startingRegion) { if (!enableAvailableChecks || !GameInteractor::IsSaveLoaded()) { return; } @@ -2065,6 +2095,20 @@ void RecalculateAvailableChecks(RandomizerRegion startingRegion /* = RR_ROOT */) const auto& ctx = Rando::Context::GetInstance(); logic = ctx->GetLogic(); + int16_t entranceIndex = gPlayState->nextEntranceIndex; + if (startingRegion == RR_ROOT && entranceIndex >= 0 && entranceIndex < ENTR_MAX) { + // Try to find a mapped entrance + // e.g. ENTR_DEKU_TREE_0_1 (index 1) is not mapped, but ENTR_DEKU_TREE_ENTRANCE (index 0) is mapped + const int8_t scene = gEntranceTable[entranceIndex].scene; + for (; entranceIndex >= 0 && gEntranceTable[entranceIndex].scene == scene; entranceIndex--) { + const auto entrance = Rando::EntranceShuffler::GetEntranceByIndex(entranceIndex); + if (entrance != nullptr) { + startingRegion = entrance->GetOriginalConnectedRegionKey(); + break; + } + } + } + std::vector targetLocations; targetLocations.reserve(RC_MAX); for (auto& location : Rando::StaticData::GetLocationTable()) { @@ -2099,6 +2143,11 @@ void RecalculateAvailableChecks(RandomizerRegion startingRegion /* = RR_ROOT */) GetPerformanceTimer(PT_RECALCULATE_AVAILABLE_CHECKS).count()); } +void RecalculateAvailableChecks(RandomizerRegion startingRegion /* = RR_ROOT */) { + recalculateAvailable = true; + availableChecksStartingRegion = startingRegion; +} + void CheckTracker_LoadFromPreset(nlohmann::json info) { presetLoaded = true; presetPos = { info["pos"]["x"], info["pos"]["y"] }; diff --git a/soh/soh/Enhancements/randomizer/randomizer_entrance.c b/soh/soh/Enhancements/randomizer/randomizer_entrance.c index 82082a9d044..e784a190eec 100644 --- a/soh/soh/Enhancements/randomizer/randomizer_entrance.c +++ b/soh/soh/Enhancements/randomizer/randomizer_entrance.c @@ -329,7 +329,8 @@ void Entrance_SetGameOverEntrance(void) { s16 scene = gPlayState->sceneNum; // When in a boss room and boss shuffle is on, use the boss scene to find the death warp entrance - if (Randomizer_GetSettingValue(RSK_SHUFFLE_BOSS_ENTRANCES) != RO_BOSS_ROOM_ENTRANCE_SHUFFLE_OFF && + if ((Randomizer_GetSettingValue(RSK_SHUFFLE_BOSS_ENTRANCES) != RO_BOSS_ROOM_ENTRANCE_SHUFFLE_OFF || + CVarGetInteger(CVAR_REMOTE_ARCHIPELAGO("ShuffleEntrances"), 0)) && scene >= SCENE_DEKU_TREE_BOSS && scene <= SCENE_SHADOW_TEMPLE_BOSS) { // Normalize boss scene range to 0 on lookup and handle for grotto entrances gSaveContext.entranceIndex = @@ -375,7 +376,8 @@ void Entrance_SetSavewarpEntrance(void) { s16 scene = gSaveContext.savedSceneNum; // When in a boss room and boss shuffle is on, use the boss scene to find the savewarp entrance - if (Randomizer_GetSettingValue(RSK_SHUFFLE_BOSS_ENTRANCES) != RO_BOSS_ROOM_ENTRANCE_SHUFFLE_OFF && + if ((Randomizer_GetSettingValue(RSK_SHUFFLE_BOSS_ENTRANCES) != RO_BOSS_ROOM_ENTRANCE_SHUFFLE_OFF || + CVarGetInteger(CVAR_REMOTE_ARCHIPELAGO("ShuffleEntrances"), 0)) && scene >= SCENE_DEKU_TREE_BOSS && scene <= SCENE_SHADOW_TEMPLE_BOSS) { // Normalize boss scene range to 0 on lookup and handle for grotto entrances gSaveContext.entranceIndex = @@ -532,7 +534,9 @@ void Entrance_HandleEponaState(void) { Player* player = GET_PLAYER(gPlayState); // If Link is riding Epona but he's about to go through an entrance where she can't spawn, // unset the Epona flag to avoid Master glitch, and restore temp B. - if (Randomizer_GetSettingValue(RSK_SHUFFLE_OVERWORLD_ENTRANCES) && (player->stateFlags1 & PLAYER_STATE1_ON_HORSE)) { + if ((Randomizer_GetSettingValue(RSK_SHUFFLE_OVERWORLD_ENTRANCES) || + CVarGetInteger(CVAR_REMOTE_ARCHIPELAGO("ShuffleEntrances"), 0)) && + (player->stateFlags1 & PLAYER_STATE1_ON_HORSE)) { // Allow Master glitch to be performed on the Thieves Hideout entrance if (entrance == Entrance_GetOverride(ENTR_THIEVES_HIDEOUT_4)) { // Gerudo Fortress -> Theives Hideout return; @@ -684,7 +688,8 @@ void Entrance_OverrideGerudoGuardCapture(void) { gPlayState->nextEntranceIndex = ENTR_GERUDO_VALLEY_1; // Gerudo Valley thrown out } - if ((LINK_IS_CHILD || Randomizer_GetSettingValue(RSK_SHUFFLE_OVERWORLD_ENTRANCES)) && + if ((LINK_IS_CHILD || (Randomizer_GetSettingValue(RSK_SHUFFLE_OVERWORLD_ENTRANCES) || + CVarGetInteger(CVAR_REMOTE_ARCHIPELAGO("ShuffleEntrances"), 0))) && gPlayState->nextEntranceIndex == ENTR_GERUDO_VALLEY_1) { // Gerudo Valley thrown out if (gPlayState->sceneNum != SCENE_GERUDO_VALLEY) { // Gerudo Valley gPlayState->nextEntranceIndex = ENTR_GERUDOS_FORTRESS_EAST_EXIT; // Gerudo Fortress @@ -701,7 +706,8 @@ void Entrance_OverrideSpawnScene(s32 sceneNum, s32 spawn) { modifiedLinkActorEntry.rot = gPlayState->linkActorEntry->rot; modifiedLinkActorEntry.params = gPlayState->linkActorEntry->params; - if (Randomizer_GetSettingValue(RSK_SHUFFLE_DUNGEON_ENTRANCES) == RO_DUNGEON_ENTRANCE_SHUFFLE_ON_PLUS_GANON) { + if (Randomizer_GetSettingValue(RSK_SHUFFLE_DUNGEON_ENTRANCES) == RO_DUNGEON_ENTRANCE_SHUFFLE_ON_PLUS_GANON || + CVarGetInteger(CVAR_REMOTE_ARCHIPELAGO("ShuffleEntrances"), 0)) { // Move Hyrule's Castle Courtyard exit spawn to be before the crates so players don't skip Talon if (sceneNum == SCENE_HYRULE_CASTLE && spawn == 1) { modifiedLinkActorEntry.pos.x = 0x033A; @@ -722,7 +728,8 @@ void Entrance_OverrideSpawnScene(s32 sceneNum, s32 spawn) { } } - if (Randomizer_GetSettingValue(RSK_SHUFFLE_BOSS_ENTRANCES) != RO_BOSS_ROOM_ENTRANCE_SHUFFLE_OFF) { + if (Randomizer_GetSettingValue(RSK_SHUFFLE_BOSS_ENTRANCES) != RO_BOSS_ROOM_ENTRANCE_SHUFFLE_OFF || + CVarGetInteger(CVAR_REMOTE_ARCHIPELAGO("ShuffleEntrances"), 0)) { // Repair the authentically bugged entrance when leaving Barniades boss room -> JabuJabu's belly // Link's position needs to be adjusted to prevent him from falling through the floor if (sceneNum == SCENE_JABU_JABU && spawn == 1) { @@ -753,7 +760,8 @@ void Entrance_OverrideSpawnScene(s32 sceneNum, s32 spawn) { } s32 Entrance_OverrideSpawnSceneRoom(s32 sceneNum, s32 spawn, s32 roomNum) { - if (Randomizer_GetSettingValue(RSK_SHUFFLE_BOSS_ENTRANCES) != RO_BOSS_ROOM_ENTRANCE_SHUFFLE_OFF) { + if (Randomizer_GetSettingValue(RSK_SHUFFLE_BOSS_ENTRANCES) != RO_BOSS_ROOM_ENTRANCE_SHUFFLE_OFF || + CVarGetInteger(CVAR_REMOTE_ARCHIPELAGO("ShuffleEntrances"), 0)) { // Repair the authentically bugged scene/spawn info for leaving Barinade's boss room -> JabuJabu's belly // to load the correct room outside Barniade's boss room if (sceneNum == SCENE_JABU_JABU && spawn == 1) { @@ -820,7 +828,6 @@ void Entrance_SetEntranceDiscovered(u16 entranceIndex, u8 isReversedEntrance) { if (idx < SAVEFILE_ENTRANCES_DISCOVERED_IDX_COUNT) { u32 entranceBit = 1 << (entranceIndex - (idx * bitsPerIndex)); gSaveContext.ship.stats.entrancesDiscovered[idx] |= entranceBit; - CheckTracker_RecalculateAvailableChecks(); // Set reverse entrance when not decoupled if (!Randomizer_GetSettingValue(RSK_DECOUPLED_ENTRANCES) && !isReversedEntrance) { diff --git a/soh/soh/Enhancements/randomizer/randomizer_entrance_tracker.cpp b/soh/soh/Enhancements/randomizer/randomizer_entrance_tracker.cpp index 33b41a3dbe2..d3b4b475c91 100644 --- a/soh/soh/Enhancements/randomizer/randomizer_entrance_tracker.cpp +++ b/soh/soh/Enhancements/randomizer/randomizer_entrance_tracker.cpp @@ -635,9 +635,9 @@ void InitEntranceTrackingData() { gEntranceTrackingData = { 0 }; // Check if entrance randomization is disabled - if (!OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_SHUFFLE_ENTRANCES)) { - return; - } + // if (!OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_SHUFFLE_ENTRANCES)) { + // return; + //} // Set total and group counts for (size_t i = 0; i < ENTRANCE_OVERRIDES_MAX_COUNT; i++) { diff --git a/soh/soh/Enhancements/randomizer/randomizer_inf.h b/soh/soh/Enhancements/randomizer/randomizer_inf.h index 8742805a6f2..1140bc8f81d 100644 --- a/soh/soh/Enhancements/randomizer/randomizer_inf.h +++ b/soh/soh/Enhancements/randomizer/randomizer_inf.h @@ -1067,6 +1067,16 @@ DEFINE_RAND_INF(RAND_INF_CAN_SWIM) DEFINE_RAND_INF(RAND_INF_HAS_WALLET) +DEFINE_RAND_INF(RAND_INF_CAN_ISG) +DEFINE_RAND_INF(RAND_INF_CAN_OI) +DEFINE_RAND_INF(RAND_INF_CAN_QPA) +DEFINE_RAND_INF(RAND_INF_CAN_HESS) +DEFINE_RAND_INF(RAND_INF_CAN_SUPERSLIDE) +DEFINE_RAND_INF(RAND_INF_CAN_HOVER) +DEFINE_RAND_INF(RAND_INF_CAN_EQUIP_SWAP) +DEFINE_RAND_INF(RAND_INF_CAN_GROUND_JUMP) +DEFINE_RAND_INF(RAND_INF_CAN_WEIRDSHOT) + DEFINE_RAND_INF(RAND_INF_BEEHIVE_KF_STORMS_GROTTO_LEFT) DEFINE_RAND_INF(RAND_INF_BEEHIVE_KF_STORMS_GROTTO_RIGHT) DEFINE_RAND_INF(RAND_INF_BEEHIVE_LW_NEAR_SHORTCUTS_GROTTO_LEFT) diff --git a/soh/soh/Enhancements/randomizer/randomizer_item_tracker.cpp b/soh/soh/Enhancements/randomizer/randomizer_item_tracker.cpp index cd32cc4b144..d828b7c3546 100644 --- a/soh/soh/Enhancements/randomizer/randomizer_item_tracker.cpp +++ b/soh/soh/Enhancements/randomizer/randomizer_item_tracker.cpp @@ -807,7 +807,11 @@ void DrawItem(ItemTrackerItem item) { case RG_TRIFORCE_PIECE: actualItemId = item.id; hasItem = IS_RANDO && OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_TRIFORCE_HUNT); - itemName = "Triforce Piece"; + if (CVarGetInteger("gHoliday.Visual.HolidayPieces", 0)) { + itemName = "Christmas Ornament"; + } else { + itemName = "Triforce Piece"; + } break; case RG_GOHMA_SOUL: actualItemId = item.id; @@ -1669,7 +1673,11 @@ void ItemTrackerWindow::DrawElement() { if (CVarGetInteger(CVAR_TRACKER_ITEM("DisplayType.TriforcePieces"), SECTION_DISPLAY_HIDDEN) == SECTION_DISPLAY_SEPARATE) { - BeginFloatingWindows("Triforce Piece Tracker"); + if (CVarGetInteger("gHoliday.Visual.HolidayPieces", 0)) { + BeginFloatingWindows("Christmas Ornament Tracker"); + } else { + BeginFloatingWindows("Triforce Piece Tracker"); + } DrawItemsInRows(triforcePieces); EndFloatingWindows(); } @@ -2048,7 +2056,7 @@ void RegisterItemTrackerWidgets() { .LabelPosition(LabelPositions::Far) .Color(THEME_COLOR) .ComboMap(itemTrackerTriforcePieceTrackOptions) - .Tooltip("Customize what numbers are shown for triforce piece tracking.")); + .Tooltip("Customize what numbers are shown for Triforce Piece tracking.")); SohGui::mSohMenu->AddSearchWidget({ triforcePieceCount, "Randomizer", "Item Tracker", "General Settings" }); ocarinaButtonTracking = { .name = "Ocarina Buttons", .type = WidgetType::WIDGET_CVAR_COMBOBOX }; diff --git a/soh/soh/Enhancements/randomizer/savefile.cpp b/soh/soh/Enhancements/randomizer/savefile.cpp index 5e52d48ef51..dba904cafcf 100644 --- a/soh/soh/Enhancements/randomizer/savefile.cpp +++ b/soh/soh/Enhancements/randomizer/savefile.cpp @@ -266,6 +266,34 @@ extern "C" void Randomizer_InitSaveFile() { Flags_SetRandomizerInf(RAND_INF_CAN_SWIM); } + if (Randomizer_GetSettingValue(RSK_SHUFFLE_ISG) == RO_GENERIC_OFF) { + Flags_SetRandomizerInf(RAND_INF_CAN_ISG); + } + if (Randomizer_GetSettingValue(RSK_SHUFFLE_OI) == RO_GENERIC_OFF) { + Flags_SetRandomizerInf(RAND_INF_CAN_OI); + } + if (Randomizer_GetSettingValue(RSK_SHUFFLE_QPA) == RO_GENERIC_OFF) { + Flags_SetRandomizerInf(RAND_INF_CAN_QPA); + } + if (Randomizer_GetSettingValue(RSK_SHUFFLE_HESS) == RO_GENERIC_OFF) { + Flags_SetRandomizerInf(RAND_INF_CAN_HESS); + } + if (Randomizer_GetSettingValue(RSK_SHUFFLE_SUPERSLIDE) == RO_GENERIC_OFF) { + Flags_SetRandomizerInf(RAND_INF_CAN_SUPERSLIDE); + } + if (Randomizer_GetSettingValue(RSK_SHUFFLE_HOVER) == RO_GENERIC_OFF) { + Flags_SetRandomizerInf(RAND_INF_CAN_HOVER); + } + if (Randomizer_GetSettingValue(RSK_SHUFFLE_EQUIP_SWAP) == RO_GENERIC_OFF) { + Flags_SetRandomizerInf(RAND_INF_CAN_EQUIP_SWAP); + } + if (Randomizer_GetSettingValue(RSK_SHUFFLE_GROUND_JUMP) == RO_GENERIC_OFF) { + Flags_SetRandomizerInf(RAND_INF_CAN_GROUND_JUMP); + } + if (Randomizer_GetSettingValue(RSK_SHUFFLE_WEIRDSHOT) == RO_GENERIC_OFF) { + Flags_SetRandomizerInf(RAND_INF_CAN_WEIRDSHOT); + } + if (Randomizer_GetSettingValue(RSK_SHUFFLE_CHILD_WALLET) == RO_GENERIC_OFF) { Flags_SetRandomizerInf(RAND_INF_HAS_WALLET); } diff --git a/soh/soh/Enhancements/randomizer/settings.cpp b/soh/soh/Enhancements/randomizer/settings.cpp index 265066a7eb5..98380a8c65c 100644 --- a/soh/soh/Enhancements/randomizer/settings.cpp +++ b/soh/soh/Enhancements/randomizer/settings.cpp @@ -1,14 +1,16 @@ #include "settings.h" #include "trial.h" #include "dungeon.h" +#include "3drando/random.hpp" #include "soh/OTRGlobals.h" -#include - #include #include +#include + +#include "soh/Network/Archipelago/ArchipelagoConsoleWindow.h" namespace Rando { std::shared_ptr Settings::mInstance; @@ -122,6 +124,7 @@ void Settings::CreateOptions() { OPT_U8(RSK_SLEEPING_WATERFALL, "Sleeping Waterfall", {"Closed", "Open"}, OptionCategory::Setting, CVAR_RANDOMIZER_SETTING("SleepingWaterfall"), mOptionDescriptions[RSK_SLEEPING_WATERFALL]); OPT_U8(RSK_JABU_OPEN, "Jabu-Jabu", {"Closed", "Open"}, OptionCategory::Setting, CVAR_RANDOMIZER_SETTING("JabuJabu"), mOptionDescriptions[RSK_JABU_OPEN]); OPT_BOOL(RSK_LOCK_OVERWORLD_DOORS, "Lock Overworld Doors", CVAR_RANDOMIZER_SETTING("LockOverworldDoors"), mOptionDescriptions[RSK_LOCK_OVERWORLD_DOORS]); + OPT_BOOL(RSK_RANDOM_LOCKED_DOORS, "Randomize Locked Doors", CVAR_RANDOMIZER_SETTING("RandomLockedDoors"), mOptionDescriptions[RSK_RANDOM_LOCKED_DOORS]); OPT_U8(RSK_GERUDO_FORTRESS, "Fortress Carpenters", {"Normal", "Fast", "Free"}, OptionCategory::Setting, CVAR_RANDOMIZER_SETTING("FortressCarpenters"), mOptionDescriptions[RSK_GERUDO_FORTRESS]); OPT_U8(RSK_RAINBOW_BRIDGE, "Rainbow Bridge", {"Vanilla", "Always open", "Stones", "Medallions", "Dungeon rewards", "Dungeons", "Tokens", "Greg"}, OptionCategory::Setting, CVAR_RANDOMIZER_SETTING("RainbowBridge"), mOptionDescriptions[RSK_RAINBOW_BRIDGE], WidgetType::Combobox, RO_BRIDGE_VANILLA, false, IMFLAG_NONE); OPT_U8(RSK_RAINBOW_BRIDGE_STONE_COUNT, "Bridge Stone Count", {NumOpts(0, 4)}, OptionCategory::Setting, CVAR_RANDOMIZER_SETTING("StoneCount"), "", WidgetType::Slider, 3, true); @@ -216,6 +219,15 @@ void Settings::CreateOptions() { OPT_BOOL(RSK_SHUFFLE_SWIM, "Shuffle Swim", CVAR_RANDOMIZER_SETTING("ShuffleSwim"), mOptionDescriptions[RSK_SHUFFLE_SWIM]); OPT_BOOL(RSK_SHUFFLE_WEIRD_EGG, "Shuffle Weird Egg", CVAR_RANDOMIZER_SETTING("ShuffleWeirdEgg"), mOptionDescriptions[RSK_SHUFFLE_WEIRD_EGG]); OPT_BOOL(RSK_SHUFFLE_GERUDO_MEMBERSHIP_CARD, "Shuffle Gerudo Membership Card", CVAR_RANDOMIZER_SETTING("ShuffleGerudoToken"), mOptionDescriptions[RSK_SHUFFLE_GERUDO_MEMBERSHIP_CARD]); + OPT_BOOL(RSK_SHUFFLE_ISG, "Shuffle ISG", CVAR_RANDOMIZER_SETTING("ShuffleISG"), mOptionDescriptions[RSK_SHUFFLE_ISG]); + OPT_BOOL(RSK_SHUFFLE_OI, "Shuffle OI", CVAR_RANDOMIZER_SETTING("ShuffleOI"), mOptionDescriptions[RSK_SHUFFLE_OI]); + OPT_BOOL(RSK_SHUFFLE_QPA, "Shuffle QPA", CVAR_RANDOMIZER_SETTING("ShuffleQPA"), mOptionDescriptions[RSK_SHUFFLE_QPA]); + OPT_BOOL(RSK_SHUFFLE_HESS, "Shuffle HESS", CVAR_RANDOMIZER_SETTING("ShuffleHESS"), mOptionDescriptions[RSK_SHUFFLE_HESS]); + OPT_BOOL(RSK_SHUFFLE_SUPERSLIDE, "Shuffle Superslide", CVAR_RANDOMIZER_SETTING("ShuffleSuperslide"), mOptionDescriptions[RSK_SHUFFLE_SUPERSLIDE]); + OPT_BOOL(RSK_SHUFFLE_HOVER, "Shuffle Hovering", CVAR_RANDOMIZER_SETTING("ShuffleHover"), mOptionDescriptions[RSK_SHUFFLE_HOVER]); + OPT_BOOL(RSK_SHUFFLE_EQUIP_SWAP, "Shuffle Equip Swap", CVAR_RANDOMIZER_SETTING("ShuffleEquipSwap"), mOptionDescriptions[RSK_SHUFFLE_EQUIP_SWAP]); + OPT_BOOL(RSK_SHUFFLE_GROUND_JUMP, "Shuffle Ground Jump", CVAR_RANDOMIZER_SETTING("ShuffleGroundJump"), mOptionDescriptions[RSK_SHUFFLE_GROUND_JUMP]); + OPT_BOOL(RSK_SHUFFLE_WEIRDSHOT, "Shuffle Weirdshot", CVAR_RANDOMIZER_SETTING("ShuffleWeirdshot"), mOptionDescriptions[RSK_SHUFFLE_WEIRDSHOT]); OPT_U8(RSK_SHUFFLE_POTS, "Shuffle Pots", {"Off", "Dungeons", "Overworld", "All Pots"}, OptionCategory::Setting, CVAR_RANDOMIZER_SETTING("ShufflePots"), mOptionDescriptions[RSK_SHUFFLE_POTS], WidgetType::Combobox, RO_SHUFFLE_POTS_OFF); OPT_U8(RSK_SHUFFLE_GRASS, "Shuffle Grass", {"Off", "Dungeons", "Overworld", "All Grass/Bushes"}, OptionCategory::Setting, CVAR_RANDOMIZER_SETTING("ShuffleGrass"), mOptionDescriptions[RSK_SHUFFLE_GRASS], WidgetType::Combobox, RO_SHUFFLE_GRASS_OFF); OPT_U8(RSK_SHUFFLE_CRATES, "Shuffle Crates", {"Off", "Dungeons", "Overworld", "All Crates"}, OptionCategory::Setting, CVAR_RANDOMIZER_SETTING("ShuffleCrates"), mOptionDescriptions[RSK_SHUFFLE_CRATES], WidgetType::Combobox, RO_SHUFFLE_CRATES_OFF); @@ -1204,6 +1216,7 @@ void Settings::CreateOptions() { &mOptions[RSK_SLEEPING_WATERFALL], &mOptions[RSK_JABU_OPEN], &mOptions[RSK_LOCK_OVERWORLD_DOORS], + &mOptions[RSK_RANDOM_LOCKED_DOORS], }, WidgetContainerType::COLUMN); mOptionGroups[RSG_WORLD_IMGUI] = OptionGroup::SubGroup("World Settings", @@ -1273,6 +1286,15 @@ void Settings::CreateOptions() { &mOptions[RSK_SHUFFLE_DEKU_STICK_BAG], &mOptions[RSK_SHUFFLE_DEKU_NUT_BAG], &mOptions[RSK_SHUFFLE_FREESTANDING], + &mOptions[RSK_SHUFFLE_ISG], + &mOptions[RSK_SHUFFLE_OI], + &mOptions[RSK_SHUFFLE_QPA], + &mOptions[RSK_SHUFFLE_HESS], + &mOptions[RSK_SHUFFLE_SUPERSLIDE], + &mOptions[RSK_SHUFFLE_HOVER], + &mOptions[RSK_SHUFFLE_EQUIP_SWAP], + &mOptions[RSK_SHUFFLE_GROUND_JUMP], + &mOptions[RSK_SHUFFLE_WEIRDSHOT], }, WidgetContainerType::COLUMN); mOptionGroups[RSG_SHUFFLE_NPCS_IMGUI] = @@ -1482,6 +1504,7 @@ void Settings::CreateOptions() { &mOptions[RSK_SLEEPING_WATERFALL], &mOptions[RSK_JABU_OPEN], &mOptions[RSK_LOCK_OVERWORLD_DOORS], + &mOptions[RSK_RANDOM_LOCKED_DOORS], &mOptions[RSK_GERUDO_FORTRESS], &mOptions[RSK_RAINBOW_BRIDGE], &mOptions[RSK_RAINBOW_BRIDGE_STONE_COUNT], @@ -1595,6 +1618,15 @@ void Settings::CreateOptions() { &mOptions[RSK_SHUFFLE_STONE_FAIRIES], &mOptions[RSK_SHUFFLE_BEAN_FAIRIES], &mOptions[RSK_SHUFFLE_SONG_FAIRIES], + &mOptions[RSK_SHUFFLE_ISG], + &mOptions[RSK_SHUFFLE_OI], + &mOptions[RSK_SHUFFLE_QPA], + &mOptions[RSK_SHUFFLE_HESS], + &mOptions[RSK_SHUFFLE_SUPERSLIDE], + &mOptions[RSK_SHUFFLE_HOVER], + &mOptions[RSK_SHUFFLE_EQUIP_SWAP], + &mOptions[RSK_SHUFFLE_GROUND_JUMP], + &mOptions[RSK_SHUFFLE_WEIRDSHOT], }); mOptionGroups[RSG_SHUFFLE_DUNGEON_ITEMS] = OptionGroup("Shuffle Dungeon Items", { @@ -2972,6 +3004,13 @@ void Settings::ParseJson(nlohmann::json spoilerFileJson) { } } +void Settings::ResetExcludedLocations() { + const auto ctx = Context::GetInstance(); + for (int rc = 1; rc < RC_MAX; rc++) { + ctx->GetItemLocation(rc)->SetExcludedOption(RO_GENERIC_OFF); + } +} + void Settings::AssignContext(std::shared_ptr ctx) { mContext = ctx; } @@ -2993,6 +3032,78 @@ void Settings::SetAllToContext() { } } +void Settings::RandomizeAllSettings() { + // Randomize all settings except tricks + for (int i = 0; i < RSK_MAX; i++) { + switch (static_cast(i)) { + case RSK_STARTING_SKULLTULA_TOKEN: + case RSK_STARTING_HEARTS: + case RSK_STARTING_ZELDAS_LULLABY: + case RSK_STARTING_EPONAS_SONG: + case RSK_STARTING_SARIAS_SONG: + case RSK_STARTING_SUNS_SONG: + case RSK_STARTING_SONG_OF_TIME: + case RSK_STARTING_SONG_OF_STORMS: + case RSK_STARTING_MINUET_OF_FOREST: + case RSK_STARTING_BOLERO_OF_FIRE: + case RSK_STARTING_SERENADE_OF_WATER: + case RSK_STARTING_REQUIEM_OF_SPIRIT: + case RSK_STARTING_NOCTURNE_OF_SHADOW: + case RSK_STARTING_PRELUDE_OF_LIGHT: + continue; + default: + break; + } + + auto key = static_cast(i); + Option& option = mOptions[key]; + + // NEW: do not randomize locked options + if (option.IsLocked()) { + continue; + } + + if (option.GetOptionCount() == 0) { + continue; + } + + uint8_t randomIndex = Random(0, static_cast(option.GetOptionCount())); + + option.SetContextIndex(randomIndex); + if (!option.GetCVarName().empty()) { + CVarSetInteger(option.GetCVarName().c_str(), randomIndex); + } + } + + // Update option properties to handle dependencies between options + UpdateOptionProperties(); + + Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); +} + +void Settings::LockAllOptions() { + for (int i = 0; i < RSK_MAX; ++i) { + auto key = static_cast(i); + Option& option = mOptions[key]; + + // Optional: only lock real settings + if (!option.GetCVarName().empty()) { + option.SetLocked(true); + } + } +} + +void Settings::UnlockAllOptions() { + for (int i = 0; i < RSK_MAX; ++i) { + auto key = static_cast(i); + Option& option = mOptions[key]; + + if (!option.GetCVarName().empty()) { + option.SetLocked(false); + } + } +} + std::shared_ptr Settings::GetInstance() { if (mInstance == nullptr) { mInstance = std::make_shared(); diff --git a/soh/soh/Enhancements/randomizer/settings.h b/soh/soh/Enhancements/randomizer/settings.h index ed214471a51..fd6648ead9c 100644 --- a/soh/soh/Enhancements/randomizer/settings.h +++ b/soh/soh/Enhancements/randomizer/settings.h @@ -114,6 +114,9 @@ class Settings { * @param spoilerFileJson */ void ParseJson(nlohmann::json spoilerFileJson); + + void ResetExcludedLocations(); + std::map> mTricksByArea = {}; /** @@ -130,6 +133,16 @@ class Settings { */ void SetAllToContext(); + /** + * @brief Randomizes all randomizer settings (excluding tricks) to random valid values. + * This function iterates through all options and sets them to a random index within + * their valid range. + */ + void RandomizeAllSettings(); + + void LockAllOptions(); + void UnlockAllOptions(); + static std::shared_ptr GetInstance(); private: diff --git a/soh/soh/Enhancements/randomizer/static_data.cpp b/soh/soh/Enhancements/randomizer/static_data.cpp index ba2dff84749..9cd1716053f 100644 --- a/soh/soh/Enhancements/randomizer/static_data.cpp +++ b/soh/soh/Enhancements/randomizer/static_data.cpp @@ -306,4 +306,5 @@ std::unordered_map StaticData::grottoChestParamsToHint{ }; std::array StaticData::hintTextTable = {}; + } // namespace Rando diff --git a/soh/soh/Enhancements/randomizer/trial.cpp b/soh/soh/Enhancements/randomizer/trial.cpp index 81ef97a9786..8a2873330f0 100644 --- a/soh/soh/Enhancements/randomizer/trial.cpp +++ b/soh/soh/Enhancements/randomizer/trial.cpp @@ -85,6 +85,12 @@ void Trials::ParseJson(nlohmann::json spoilerFileJson) { } } +void Trials::RemoveAllTrials() { + for (auto& trial : mTrials) { + trial.SetAsSkipped(); + } +} + std::unordered_map Trials::GetAllTrialHintHeys() const { std::unordered_map output = {}; for (auto trial : mTrials) { diff --git a/soh/soh/Enhancements/randomizer/trial.h b/soh/soh/Enhancements/randomizer/trial.h index 1ca39d09645..85cc6dd176e 100644 --- a/soh/soh/Enhancements/randomizer/trial.h +++ b/soh/soh/Enhancements/randomizer/trial.h @@ -37,6 +37,7 @@ class Trials { std::vector GetTrialList(); size_t GetTrialListSize() const; void ParseJson(nlohmann::json spoilerFileJson); + void RemoveAllTrials(); std::unordered_map GetAllTrialHintHeys() const; private: diff --git a/soh/soh/Enhancements/timesaver_hook_handlers.cpp b/soh/soh/Enhancements/timesaver_hook_handlers.cpp index ff1e18354c0..fc73bb9bda4 100644 --- a/soh/soh/Enhancements/timesaver_hook_handlers.cpp +++ b/soh/soh/Enhancements/timesaver_hook_handlers.cpp @@ -301,9 +301,10 @@ void TimeSaverOnVanillaBehaviorHandler(GIVanillaBehavior id, bool* should, va_li } switch (actor->id) { case ACTOR_OBJ_SWITCH: { + // The DC boss door can be unlocked with OI; One Point is required for it + // The Water Temple Dragon Room chest can be obtained with a cutscene dive if (((actor->params == 8224 && gPlayState->sceneNum == SCENE_DODONGOS_CAVERN) || - (actor->params == 6979 && gPlayState->sceneNum == SCENE_WATER_TEMPLE) || - (actor->params == 8961 && gPlayState->sceneNum == SCENE_SPIRIT_TEMPLE)) && + (actor->params == 6979 && gPlayState->sceneNum == SCENE_WATER_TEMPLE)) && CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.GlitchAiding"), 0)) { break; } @@ -316,9 +317,12 @@ void TimeSaverOnVanillaBehaviorHandler(GIVanillaBehavior id, bool* should, va_li case ACTOR_BG_BDAN_SWITCH: { // The switch in jabu that you are intended to press with a box to reach barinade // can be skipped by either a frame perfect roll open or with OI + // Additionally, the blue switch that you are intended to press with Ruto + // can be skipped with OI // The One Point for that switch is used in common setups for the former and is required for the // latter to work - if (actor->params == 14848 && gPlayState->sceneNum == SCENE_JABU_JABU && + if ((actor->params == 14848 || actor->params == 14336) && + gPlayState->sceneNum == SCENE_JABU_JABU && CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.GlitchAiding"), 0)) { break; } @@ -343,6 +347,7 @@ void TimeSaverOnVanillaBehaviorHandler(GIVanillaBehavior id, bool* should, va_li break; } case ACTOR_EN_BOX: { + // The chest that drops in MQ Jabu allowing unintended door entry if (actor->params == -30457 && gPlayState->sceneNum == SCENE_JABU_JABU && CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.GlitchAiding"), 0)) { break; @@ -352,6 +357,17 @@ void TimeSaverOnVanillaBehaviorHandler(GIVanillaBehavior id, bool* should, va_li RateLimitedSuccessChime(); break; } + case ACTOR_EN_SIOFUKI: { + // The Spirit Temple MQ water jet cutscene is required for an actor glitch + // setup that skips the grate + if (actor->params == 6359 && gPlayState->sceneNum == SCENE_SPIRIT_TEMPLE && + CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.GlitchAiding"), 0)) { + *should = true; + break; + } + *should = false; + break; + } case ACTOR_BG_HIDAN_FWBIG: case ACTOR_EN_EX_ITEM: case ACTOR_EN_DNT_NOMAL: @@ -361,7 +377,17 @@ void TimeSaverOnVanillaBehaviorHandler(GIVanillaBehavior id, bool* should, va_li break; } case ACTOR_EN_TA: - case ACTOR_DOOR_SHUTTER: + case ACTOR_DOOR_SHUTTER: { + // The shutter cutscene occurs post-switch cutscene to focus Link on the unlocked doors + if (((actor->params == 9402 && gPlayState->sceneNum == SCENE_JABU_JABU) || + (actor->params == 20460 && gPlayState->sceneNum == SCENE_DODONGOS_CAVERN)) && + CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.GlitchAiding"), 0)) { + break; + } + *should = false; + RateLimitedSuccessChime(); + break; + } case ACTOR_BG_ICE_SHUTTER: case ACTOR_OBJ_LIGHTSWITCH: case ACTOR_OBJ_SYOKUDAI: @@ -1202,7 +1228,7 @@ void TimeSaverOnSceneInitHandler(int16_t sceneNum) { } } -static GetItemEntry vanillaQueuedItemEntry = GET_ITEM_NONE; +GetItemEntry vanillaQueuedItemEntry = GET_ITEM_NONE; void TimeSaverQueueItem(RandomizerGet randoGet) { vanillaQueuedItemEntry = Rando::StaticData::RetrieveItem(randoGet).GetGIEntry_Copy(); @@ -1214,6 +1240,12 @@ void TimeSaverOnFlagSetHandler(int16_t flagType, int16_t flag) { return; } + if (IS_ROGUELIKE && + ((flagType == FLAG_EVENT_CHECK_INF && flag == EVENTCHKINF_SPOKE_TO_SARIA_ON_BRIDGE) || + (flagType == FLAG_EVENT_CHECK_INF && flag == EVENTCHKINF_RETURNED_TO_TEMPLE_OF_TIME_WITH_ALL_MEDALLIONS))) { + return; + } + if (CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.Story"), IS_RANDO)) { switch (flagType) { case FLAG_EVENT_CHECK_INF: @@ -1260,6 +1292,10 @@ void TimeSaverOnFlagSetHandler(int16_t flagType, int16_t flag) { } } + if (IS_ROGUELIKE) { + return; + } + if (CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipMiscInteractions"), IS_RANDO)) { switch (flagType) { case FLAG_RANDOMIZER_INF: diff --git a/soh/soh/Enhancements/timesplits/TimeSplits.cpp b/soh/soh/Enhancements/timesplits/TimeSplits.cpp index 1aa15795e35..87de4a8e806 100644 --- a/soh/soh/Enhancements/timesplits/TimeSplits.cpp +++ b/soh/soh/Enhancements/timesplits/TimeSplits.cpp @@ -15,6 +15,7 @@ #include "assets/textures/parameter_static/parameter_static.h" #include #include "soh/SohGui/UIWidgets.hpp" +#include "soh/Network/Archipelago/Archipelago.h" extern "C" { #include "z64item.h" @@ -352,6 +353,7 @@ void HandleDragAndDrop(std::vector& objectList, int targetIndex, co void TimeSplitCompleteSplits() { gSaveContext.ship.stats.itemTimestamp[TIMESTAMP_DEFEAT_GANON] = GAMEPLAYSTAT_TOTAL_TIME; gSaveContext.ship.stats.gameComplete = true; + ArchipelagoClient::GetInstance().SendGameWon(); } void TimeSplitsSkipSplit(uint32_t index) { diff --git a/soh/soh/Enhancements/tts/tts.cpp b/soh/soh/Enhancements/tts/tts.cpp index 612d4172c18..dd3a3826ad5 100644 --- a/soh/soh/Enhancements/tts/tts.cpp +++ b/soh/soh/Enhancements/tts/tts.cpp @@ -853,7 +853,7 @@ void RegisterOnUpdateMainMenuSelection() { return; uint8_t language = (gSaveContext.language == LANGUAGE_JPN) ? LANGUAGE_ENG : gSaveContext.language; - auto optionName = SohFileSelect_GetSettingText(optionIndex, language); + auto optionName = SohFileSelect_GetRandomizerSettingText(optionIndex, language); SpeechSynthesizer::Instance->Speak(optionName, GetLanguageCode()); }); diff --git a/soh/soh/Network/Anchor/Anchor.cpp b/soh/soh/Network/Anchor/Anchor.cpp index 5852e62e07f..ccaedb4c6f5 100644 --- a/soh/soh/Network/Anchor/Anchor.cpp +++ b/soh/soh/Network/Anchor/Anchor.cpp @@ -40,6 +40,26 @@ void Anchor::OnDisconnected() { RegisterHooks(); } +void Anchor::ProcessOutgoingPackets() { + // Copy all queued packets while holding the lock, then send them after releasing + std::queue packetsToSend; + { + std::lock_guard lock(outgoingPacketQueueMutex); + packetsToSend.swap(outgoingPacketQueue); + } + + // Send packets without holding the lock + while (!packetsToSend.empty()) { + nlohmann::json payload = packetsToSend.front(); + packetsToSend.pop(); + + if (!payload.contains("quiet")) { + SPDLOG_DEBUG("[Anchor] Sending payload:\n{}", payload.dump()); + } + Network::SendJsonToRemote(payload); + } +} + void Anchor::SendJsonToRemote(nlohmann::json payload) { if (!isConnected) { return; @@ -47,9 +67,17 @@ void Anchor::SendJsonToRemote(nlohmann::json payload) { payload["clientId"] = ownClientId; if (!payload.contains("quiet")) { - SPDLOG_DEBUG("[Anchor] Sending payload:\n{}", payload.dump()); + SPDLOG_DEBUG("[Anchor] Queuing payload:\n{}", payload.dump()); } - Network::SendJsonToRemote(payload); + + if (payload["type"] == HANDSHAKE) { + Network::SendJsonToRemote(payload); + return; + } + + // Queue the packet to be sent on the network thread + std::lock_guard lock(outgoingPacketQueueMutex); + outgoingPacketQueue.push(payload); } void Anchor::OnIncomingJson(nlohmann::json payload) { @@ -65,8 +93,8 @@ void Anchor::OnIncomingJson(nlohmann::json payload) { std::string packetType = payload["type"].get(); - // Ignore packets from mismatched clients, except for ALL_CLIENT_STATE or UPDATE_CLIENT_STATE - if (packetType != ALL_CLIENT_STATE && packetType != UPDATE_CLIENT_STATE) { + // Ignore packets from mismatched clients, except for ALL_CLIENT_STATE, UPDATE_CLIENT_STATE, and PLAYER_UPDATE + if (packetType != ALL_CLIENT_STATE && packetType != UPDATE_CLIENT_STATE && packetType != PLAYER_UPDATE) { if (payload.contains("clientId")) { uint32_t clientId = payload["clientId"].get(); if (clients.contains(clientId) && clients[clientId].clientVersion != clientVersion) { @@ -75,69 +103,76 @@ void Anchor::OnIncomingJson(nlohmann::json payload) { } } - // Handle PLAYER_UPDATE packets immediately, no need to queue - if (packetType == PLAYER_UPDATE) { - HandlePacket_PlayerUpdate(payload); - return; - } - // Queue all packets to be processed on the game thread std::lock_guard lock(incomingPacketQueueMutex); incomingPacketQueue.push(payload); } void Anchor::ProcessIncomingPacketQueue() { - std::lock_guard lock(incomingPacketQueueMutex); + // Copy all queued packets while holding the lock, then process them after releasing + std::queue packetsToProcess; + { + std::lock_guard lock(incomingPacketQueueMutex); + packetsToProcess.swap(incomingPacketQueue); + } - while (!incomingPacketQueue.empty()) { - nlohmann::json payload = incomingPacketQueue.front(); - incomingPacketQueue.pop(); + // Process packets without holding the lock + while (!packetsToProcess.empty()) { + nlohmann::json payload = packetsToProcess.front(); + packetsToProcess.pop(); std::string packetType = payload["type"].get(); isProcessingIncomingPacket = true; - // packetType here is a string so we can't use a switch statement - if (packetType == ALL_CLIENT_STATE) - HandlePacket_AllClientState(payload); - else if (packetType == DAMAGE_PLAYER) - HandlePacket_DamagePlayer(payload); - else if (packetType == DISABLE_ANCHOR) - HandlePacket_DisableAnchor(payload); - else if (packetType == ENTRANCE_DISCOVERED) - HandlePacket_EntranceDiscovered(payload); - else if (packetType == GAME_COMPLETE) - HandlePacket_GameComplete(payload); - else if (packetType == GIVE_ITEM) - HandlePacket_GiveItem(payload); - else if (packetType == OCARINA_SFX) - HandlePacket_OcarinaSfx(payload); - else if (packetType == PLAYER_SFX) - HandlePacket_PlayerSfx(payload); - else if (packetType == UPDATE_TEAM_STATE) - HandlePacket_UpdateTeamState(payload); - else if (packetType == REQUEST_TEAM_STATE) - HandlePacket_RequestTeamState(payload); - else if (packetType == REQUEST_TELEPORT) - HandlePacket_RequestTeleport(payload); - else if (packetType == SERVER_MESSAGE) - HandlePacket_ServerMessage(payload); - else if (packetType == SET_CHECK_STATUS) - HandlePacket_SetCheckStatus(payload); - else if (packetType == SET_FLAG) - HandlePacket_SetFlag(payload); - else if (packetType == TELEPORT_TO) - HandlePacket_TeleportTo(payload); - else if (packetType == UNSET_FLAG) - HandlePacket_UnsetFlag(payload); - else if (packetType == UPDATE_BEANS_COUNT) - HandlePacket_UpdateBeansCount(payload); - else if (packetType == UPDATE_CLIENT_STATE) - HandlePacket_UpdateClientState(payload); - else if (packetType == UPDATE_ROOM_STATE) - HandlePacket_UpdateRoomState(payload); - else if (packetType == UPDATE_DUNGEON_ITEMS) - HandlePacket_UpdateDungeonItems(payload); + try { + // packetType here is a string so we can't use a switch statement + if (packetType == ALL_CLIENT_STATE) + HandlePacket_AllClientState(payload); + else if (packetType == DAMAGE_PLAYER) + HandlePacket_DamagePlayer(payload); + else if (packetType == DISABLE_ANCHOR) + HandlePacket_DisableAnchor(payload); + else if (packetType == ENTRANCE_DISCOVERED) + HandlePacket_EntranceDiscovered(payload); + else if (packetType == GAME_COMPLETE) + HandlePacket_GameComplete(payload); + else if (packetType == GIVE_ITEM) + HandlePacket_GiveItem(payload); + else if (packetType == OCARINA_SFX) + HandlePacket_OcarinaSfx(payload); + else if (packetType == PLAYER_UPDATE) + HandlePacket_PlayerUpdate(payload); + else if (packetType == PLAYER_SFX) + HandlePacket_PlayerSfx(payload); + else if (packetType == UPDATE_TEAM_STATE) + HandlePacket_UpdateTeamState(payload); + else if (packetType == REQUEST_TEAM_STATE) + HandlePacket_RequestTeamState(payload); + else if (packetType == REQUEST_TELEPORT) + HandlePacket_RequestTeleport(payload); + else if (packetType == SERVER_MESSAGE) + HandlePacket_ServerMessage(payload); + else if (packetType == SET_CHECK_STATUS) + HandlePacket_SetCheckStatus(payload); + else if (packetType == SET_FLAG) + HandlePacket_SetFlag(payload); + else if (packetType == TELEPORT_TO) + HandlePacket_TeleportTo(payload); + else if (packetType == UNSET_FLAG) + HandlePacket_UnsetFlag(payload); + else if (packetType == UPDATE_BEANS_COUNT) + HandlePacket_UpdateBeansCount(payload); + else if (packetType == UPDATE_CLIENT_STATE) + HandlePacket_UpdateClientState(payload); + else if (packetType == UPDATE_ROOM_STATE) + HandlePacket_UpdateRoomState(payload); + else if (packetType == UPDATE_DUNGEON_ITEMS) + HandlePacket_UpdateDungeonItems(payload); + } catch (const std::exception& e) { + SPDLOG_ERROR("[Anchor] Exception while processing incoming packet {}", e.what()); + SPDLOG_ERROR("[Anchor] Packet: {}", payload.dump()); + } isProcessingIncomingPacket = false; } diff --git a/soh/soh/Network/Anchor/Anchor.h b/soh/soh/Network/Anchor/Anchor.h index 9eb10323091..8476d8c3649 100644 --- a/soh/soh/Network/Anchor/Anchor.h +++ b/soh/soh/Network/Anchor/Anchor.h @@ -76,6 +76,8 @@ class Anchor : public Network { bool isProcessingIncomingPacket = false; std::queue incomingPacketQueue; std::mutex incomingPacketQueueMutex; + std::queue outgoingPacketQueue; + std::mutex outgoingPacketQueueMutex; nlohmann::json PrepClientState(); nlohmann::json PrepRoomState(); @@ -108,7 +110,7 @@ class Anchor : public Network { public: uint32_t ownClientId; - inline static const std::string clientVersion = (char*)gBuildVersion; + inline static const std::string clientVersion = (char*)gGitCommitHash; // Packet types // inline static const std::string ALL_CLIENT_STATE = "ALL_CLIENT_STATE"; @@ -143,6 +145,7 @@ class Anchor : public Network { void OnIncomingJson(nlohmann::json payload); void OnConnected(); void OnDisconnected(); + void ProcessOutgoingPackets(); void DrawMenu(); void ProcessIncomingPacketQueue(); void SendJsonToRemote(nlohmann::json packet); diff --git a/soh/soh/Network/Anchor/DummyPlayer.cpp b/soh/soh/Network/Anchor/DummyPlayer.cpp index 8e5df3c8e5f..9192c3c024a 100644 --- a/soh/soh/Network/Anchor/DummyPlayer.cpp +++ b/soh/soh/Network/Anchor/DummyPlayer.cpp @@ -158,13 +158,13 @@ void DummyPlayer_Update(Actor* actor, PlayState* play) { player->actor.world.pos.y += diff.y * player->actor.scale.y; } - if (player->modelGroup != Player_ActionToModelGroup(player, player->itemAction)) { + if (player->modelGroup != client.modelGroup) { // Hack to account for usage of gSaveContext s32 originalAge = gSaveContext.linkAge; gSaveContext.linkAge = client.linkAge; u8 originalButtonItem0 = gSaveContext.equips.buttonItems[0]; gSaveContext.equips.buttonItems[0] = client.buttonItem0; - Player_SetModelGroup(player, Player_ActionToModelGroup(player, player->itemAction)); + Player_SetModelGroup(player, client.modelGroup); gSaveContext.linkAge = originalAge; gSaveContext.equips.buttonItems[0] = originalButtonItem0; } diff --git a/soh/soh/Network/Anchor/JsonConversions.hpp b/soh/soh/Network/Anchor/JsonConversions.hpp index b99b7849647..4f218bea6a9 100644 --- a/soh/soh/Network/Anchor/JsonConversions.hpp +++ b/soh/soh/Network/Anchor/JsonConversions.hpp @@ -52,18 +52,18 @@ inline void from_json(const json& j, PosRot& posRot) { } inline void from_json(const json& j, AnchorClient& client) { - j.contains("clientId") ? j.at("clientId").get_to(client.clientId) : client.clientId = 0; - j.contains("name") ? j.at("name").get_to(client.name) : client.name = "???"; - j.contains("color") ? j.at("color").get_to(client.color) : client.color = { 255, 255, 255 }; - j.contains("clientVersion") ? j.at("clientVersion").get_to(client.clientVersion) : client.clientVersion = "???"; - j.contains("teamId") ? j.at("teamId").get_to(client.teamId) : client.teamId = "default"; - j.contains("online") ? j.at("online").get_to(client.online) : client.online = false; - j.contains("seed") ? j.at("seed").get_to(client.seed) : client.seed = 0; - j.contains("isSaveLoaded") ? j.at("isSaveLoaded").get_to(client.isSaveLoaded) : client.isSaveLoaded = false; - j.contains("isGameComplete") ? j.at("isGameComplete").get_to(client.isGameComplete) : client.isGameComplete = false; - j.contains("sceneNum") ? j.at("sceneNum").get_to(client.sceneNum) : client.sceneNum = SCENE_ID_MAX; - j.contains("entranceIndex") ? j.at("entranceIndex").get_to(client.entranceIndex) : client.entranceIndex = 0; - j.contains("self") ? j.at("self").get_to(client.self) : client.self = false; + client.clientId = j.value("clientId", (u32)0); + client.name = j.value("name", "???"); + client.color = j.value("color", Color_RGB8{ 255, 255, 255 }); + client.clientVersion = j.value("clientVersion", "???"); + client.teamId = j.value("teamId", "default"); + client.online = j.value("online", false); + client.seed = j.value("seed", (u32)0); + client.isSaveLoaded = j.value("isSaveLoaded", false); + client.isGameComplete = j.value("isGameComplete", false); + client.sceneNum = j.value("sceneNum", (s16)SCENE_ID_MAX); + client.entranceIndex = j.value("entranceIndex", (s32)0); + client.self = j.value("self", false); } inline void to_json(json& j, const Inventory& inventory) { @@ -105,11 +105,13 @@ inline void from_json(const json& j, SohStats& sohStats) { inline void to_json(json& j, const ShipRandomizerSaveContextData& shipRandomizerSaveContextData) { j = json{ { "triforcePiecesCollected", shipRandomizerSaveContextData.triforcePiecesCollected }, + { "bombchuUpgradeLevel", shipRandomizerSaveContextData.bombchuUpgradeLevel }, }; } inline void from_json(const json& j, ShipRandomizerSaveContextData& shipRandomizerSaveContextData) { j.at("triforcePiecesCollected").get_to(shipRandomizerSaveContextData.triforcePiecesCollected); + j.at("bombchuUpgradeLevel").get_to(shipRandomizerSaveContextData.bombchuUpgradeLevel); } inline void to_json(json& j, const ShipQuestSpecificSaveContextData& shipQuestSpecificSaveContextData) { diff --git a/soh/soh/Network/Anchor/Menu.cpp b/soh/soh/Network/Anchor/Menu.cpp index 77b544fb3df..4f6c6099815 100644 --- a/soh/soh/Network/Anchor/Menu.cpp +++ b/soh/soh/Network/Anchor/Menu.cpp @@ -129,6 +129,9 @@ void AnchorMainMenu(WidgetInfo& info) { ImGui::SeparatorText("Current Room"); ImGui::Text("%s Connected", ICON_FA_CHECK); + if (IS_ARCHIPELAGO) + ImGui::BeginDisabled(); + UIWidgets::PushStyleButton(THEME_COLOR); if (ImGui::Button("Request Team State")) { anchor->SendPacket_RequestTeamState(); @@ -136,6 +139,9 @@ void AnchorMainMenu(WidgetInfo& info) { UIWidgets::Tooltip("Try this if you are missing items or flags that your team members have collected"); UIWidgets::PopStyleButton(); + if (IS_ARCHIPELAGO) + ImGui::EndDisabled(); + ImGui::SameLine(); UIWidgets::WindowButton("Toggle Anchor Room Window", CVAR_WINDOW("AnchorRoom"), SohGui::mAnchorRoomWindow); @@ -189,10 +195,19 @@ void AnchorAdminMenu(WidgetInfo& info) { .Color(THEME_COLOR))) { anchor->SendPacket_UpdateRoomState(); } + + if (ArchipelagoClient::GetInstance().IsConnected()) { // slot connected + ImGui::BeginDisabled(); + } + if (UIWidgets::CVarCheckbox("Sync Items & Flags", CVAR_REMOTE_ANCHOR("RoomSettings.SyncItemsAndFlags"), UIWidgets::CheckboxOptions().DefaultValue(true).Color(THEME_COLOR))) { anchor->SendPacket_UpdateRoomState(); } + + if (ArchipelagoClient::GetInstance().IsConnected()) { // slot connected + ImGui::EndDisabled(); + } } void AnchorInstructionsMenu(WidgetInfo& info) { diff --git a/soh/soh/Network/Anchor/Packets/AllClientState.cpp b/soh/soh/Network/Anchor/Packets/AllClientState.cpp index 08d8d1a55d5..43cdf3f6a85 100644 --- a/soh/soh/Network/Anchor/Packets/AllClientState.cpp +++ b/soh/soh/Network/Anchor/Packets/AllClientState.cpp @@ -67,5 +67,5 @@ void Anchor::HandlePacket_AllClientState(nlohmann::json payload) { clients.erase(clientId); } - RefreshClientActors(); + shouldRefreshActors = true; } diff --git a/soh/soh/Network/Anchor/Packets/GiveItem.cpp b/soh/soh/Network/Anchor/Packets/GiveItem.cpp index fbf048d7d25..bd3828f7bae 100644 --- a/soh/soh/Network/Anchor/Packets/GiveItem.cpp +++ b/soh/soh/Network/Anchor/Packets/GiveItem.cpp @@ -98,10 +98,22 @@ void Anchor::HandlePacket_GiveItem(nlohmann::json payload) { .suffix = SohUtils::GetItemName(getItemEntry.itemId), }); } else if (getItemEntry.modIndex == MOD_RANDOMIZER) { + std::string itemName = + Rando::StaticData::RetrieveItem((RandomizerGet)getItemEntry.getItemId).GetName().english; + + if ((RandomizerGet)getItemEntry.getItemId == RG_TRIFORCE_PIECE) { + if (CVarGetInteger("gHoliday.Visual.HolidayPieces", 0)) { + + itemName = "a Christmas Ornament"; + } else { + itemName = "Triforce Piece"; + } + } + Notification::Emit({ .prefix = client.name, .message = "found", - .suffix = Rando::StaticData::RetrieveItem((RandomizerGet)getItemEntry.getItemId).GetName().english, + .suffix = itemName, }); } } diff --git a/soh/soh/Network/Anchor/Packets/PlayerUpdate.cpp b/soh/soh/Network/Anchor/Packets/PlayerUpdate.cpp index c05de0a23c2..ad373e38e03 100644 --- a/soh/soh/Network/Anchor/Packets/PlayerUpdate.cpp +++ b/soh/soh/Network/Anchor/Packets/PlayerUpdate.cpp @@ -58,7 +58,7 @@ void Anchor::SendPacket_PlayerUpdate() { payload["currentShield"] = player->currentShield; payload["currentTunic"] = player->currentTunic; payload["stateFlags1"] = player->stateFlags1; - payload["stateFlags2"] = player->stateFlags2; + payload["stateFlags2"] = player->stateFlags2 & ~PLAYER_STATE2_DISABLE_DRAW; payload["buttonItem0"] = gSaveContext.equips.buttonItems[0]; payload["itemAction"] = player->itemAction; payload["heldItemAction"] = player->heldItemAction; @@ -83,35 +83,36 @@ void Anchor::HandlePacket_PlayerUpdate(nlohmann::json payload) { if (clients.contains(clientId)) { auto& client = clients[clientId]; - if (client.linkAge != payload["linkAge"].get()) { + if (client.linkAge != payload.value("linkAge", (s32)LINK_AGE_ADULT)) { shouldRefreshActors = true; } - client.sceneNum = payload["sceneNum"].get(); - client.entranceIndex = payload["entranceIndex"].get(); - client.linkAge = payload["linkAge"].get(); - client.posRot = payload["posRot"].get(); - std::vector jointArray = payload["jointTable"]; + client.sceneNum = payload.value("sceneNum", (s16)SCENE_ID_MAX); + client.entranceIndex = payload.value("entranceIndex", (s32)0); + client.linkAge = payload.value("linkAge", (s32)LINK_AGE_ADULT); + client.posRot = payload.value("posRot", PosRot{ 0 }); + std::vector jointArray = payload.value("jointTable", std::vector{}); + jointArray.resize(24 * 3); // Ensure it has enough elements, in case of missing data for (int i = 0; i < 24; i++) { client.jointTable[i].x = jointArray[i * 3]; client.jointTable[i].y = jointArray[i * 3 + 1]; client.jointTable[i].z = jointArray[i * 3 + 2]; } - client.movementFlags = payload["movementFlags"].get(); - client.prevTransl = payload["prevTransl"].get(); - client.upperLimbRot = payload["upperLimbRot"].get(); - client.currentBoots = payload["currentBoots"].get(); - client.currentShield = payload["currentShield"].get(); - client.currentTunic = payload["currentTunic"].get(); - client.stateFlags1 = payload["stateFlags1"].get(); - client.stateFlags2 = payload["stateFlags2"].get(); - client.buttonItem0 = payload["buttonItem0"].get(); - client.itemAction = payload["itemAction"].get(); - client.heldItemAction = payload["heldItemAction"].get(); - client.modelGroup = payload["modelGroup"].get(); - client.invincibilityTimer = payload["invincibilityTimer"].get(); - client.unk_862 = payload["unk_862"].get(); - client.unk_85C = payload["unk_85C"].get(); - client.actionVar1 = payload["actionVar1"].get(); + client.movementFlags = payload.value("movementFlags", (u8)0); + client.prevTransl = payload.value("prevTransl", Vec3s{ 0 }); + client.upperLimbRot = payload.value("upperLimbRot", Vec3s{ 0 }); + client.currentBoots = payload.value("currentBoots", (s8)0); + client.currentShield = payload.value("currentShield", (s8)0); + client.currentTunic = payload.value("currentTunic", (s8)0); + client.stateFlags1 = payload.value("stateFlags1", (u32)0); + client.stateFlags2 = payload.value("stateFlags2", (u32)0); + client.buttonItem0 = payload.value("buttonItem0", (u8)0); + client.itemAction = payload.value("itemAction", (s8)0); + client.heldItemAction = payload.value("heldItemAction", (s8)0); + client.modelGroup = payload.value("modelGroup", (u8)0); + client.invincibilityTimer = payload.value("invincibilityTimer", (s8)0); + client.unk_862 = payload.value("unk_862", (s16)0); + client.unk_85C = payload.value("unk_85C", (f32)0); + client.actionVar1 = payload.value("actionVar1", (s8)0); } } diff --git a/soh/soh/Network/Anchor/Packets/SetCheckStatus.cpp b/soh/soh/Network/Anchor/Packets/SetCheckStatus.cpp index 74bc4469915..fb1c70dc9a3 100644 --- a/soh/soh/Network/Anchor/Packets/SetCheckStatus.cpp +++ b/soh/soh/Network/Anchor/Packets/SetCheckStatus.cpp @@ -4,6 +4,8 @@ #include "soh/Enhancements/game-interactor/GameInteractor.h" #include "soh/OTRGlobals.h" +static bool isResultOfHandling = false; + /** * SET_CHECK_STATUS * @@ -11,7 +13,7 @@ */ void Anchor::SendPacket_SetCheckStatus(RandomizerCheck rc) { - if (!IsSaveLoaded() || isProcessingIncomingPacket || !roomState.syncItemsAndFlags) { + if (!IsSaveLoaded() || isResultOfHandling) { return; } @@ -40,12 +42,16 @@ void Anchor::HandlePacket_SetCheckStatus(nlohmann::json payload) { RandomizerCheckStatus status = payload["status"].get(); bool skipped = payload["skipped"].get(); + isResultOfHandling = true; + if (randoContext->GetItemLocation(rc)->GetCheckStatus() != status) { randoContext->GetItemLocation(rc)->SetCheckStatus(status); } if (randoContext->GetItemLocation(rc)->GetIsSkipped() != skipped) { randoContext->GetItemLocation(rc)->SetIsSkipped(skipped); } + CheckTracker::RecalculateAllAreaTotals(); CheckTracker::RecalculateAvailableChecks(); + isResultOfHandling = false; } diff --git a/soh/soh/Network/Anchor/Packets/UpdateTeamState.cpp b/soh/soh/Network/Anchor/Packets/UpdateTeamState.cpp index 9c7c3309a12..ad2bb10668a 100644 --- a/soh/soh/Network/Anchor/Packets/UpdateTeamState.cpp +++ b/soh/soh/Network/Anchor/Packets/UpdateTeamState.cpp @@ -228,7 +228,7 @@ void Anchor::HandlePacket_UpdateTeamState(nlohmann::json payload) { OTRGlobals::Instance->gRandoContext->GetItemLocation(i)->SetCheckStatus( payload["state"]["rando"]["itemLocations"][i][0].get()); OTRGlobals::Instance->gRandoContext->GetItemLocation(i)->SetIsSkipped( - payload["state"]["rando"]["itemLocations"][i][0].get()); + payload["state"]["rando"]["itemLocations"][i][1].get()); // if (payload["state"]["rando"]["itemLocations"][i].contains("fakeRgID")) { // randoContext->overrides.emplace(static_cast(i), diff --git a/soh/soh/Network/Archipelago/Archipelago.cpp b/soh/soh/Network/Archipelago/Archipelago.cpp new file mode 100644 index 00000000000..a218080e78f --- /dev/null +++ b/soh/soh/Network/Archipelago/Archipelago.cpp @@ -0,0 +1,768 @@ +#include "Archipelago.h" +#include "soh/util.h" +#include +#include + +#include +#include +#include +#include +#include + +#include "soh/Network/Archipelago/ArchipelagoConsoleWindow.h" +#include "soh/Enhancements/randomizer/randomizerTypes.h" +#include "soh/Enhancements/randomizer/static_data.h" +#include "soh/Enhancements/randomizer/context.h" +#include "soh/Enhancements/game-interactor/GameInteractor.h" +#include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h" +#include "soh/Notification/Notification.h" +#include "soh/ShipInit.hpp" +#include "soh/SaveManager.h" +#include "soh/SohGui/SohGui.hpp" +#include "soh/OTRGlobals.h" +#include "soh/Network/Anchor/Anchor.h" + +extern "C" { +#include "variables.h" +#include "macros.h" +extern PlayState* gPlayState; +} + +ArchipelagoClient::ArchipelagoClient() { + uuid = ap_get_uuid(Ship::Context::GetPathRelativeToAppDirectory("uuid")); + + gameWon = false; + itemQueued = false; + disconnecting = false; + isDeathLinkedDeath = false; + uri = ""; + password = ""; +} + +ArchipelagoClient& ArchipelagoClient::GetInstance() { + static ArchipelagoClient Client; + return Client; +} + +bool ArchipelagoClient::StartClient() { + if (apClient != nullptr) { + apClient.reset(); + } + + disconnecting = false; + retries = 0; + uri = CVarGetString(CVAR_REMOTE_ARCHIPELAGO("ServerAddress"), "localhost:38281"); + password = CVarGetString(CVAR_REMOTE_ARCHIPELAGO("Password"), ""); + + const std::string cert = Ship::Context::LocateFileAcrossAppDirs("networking/cacert.pem"); + SPDLOG_DEBUG("Location of cert: " + cert); + apClient = std::unique_ptr(new APClient(uuid, AP_Client_consts::AP_GAME_NAME, uri, cert)); + + CVarSetInteger(CVAR_REMOTE_ARCHIPELAGO("ConnectionStatus"), 1); // Connecting + + apClient->set_socket_error_handler([&](const std::string& msg) { + retries++; + if (retries >= AP_Client_consts::MAX_RETRIES) { + ArchipelagoConsole_SendMessage("[ERROR] Could not connect to server after several tries.\nAre the entered " + "server address and port correct?"); + CVarSetInteger(CVAR_REMOTE_ARCHIPELAGO("ConnectionStatus"), 2); // Connection error + disconnecting = true; + + if (GameInteractor::IsSaveLoaded) { + SohGui::ShowArchipelagoSettingsMenu(); + } + return; + } + ArchipelagoConsole_SendMessage(std::string("[ERROR] " + msg).c_str()); + }); + + apClient->set_room_info_handler([&]() { + std::list tags; + if (CVarGetInteger(CVAR_REMOTE_ARCHIPELAGO("DeathLink"), 0)) { + tags.push_back("DeathLink"); + } + apClient->ConnectSlot(CVarGetString(CVAR_REMOTE_ARCHIPELAGO("SlotName"), ""), password, 0b0101, tags, + { 0, 6, 3 }); + }); + + apClient->set_slot_connected_handler([&](const nlohmann::json data) { + CVarSetInteger(CVAR_REMOTE_ARCHIPELAGO("ConnectionStatus"), 3); // slot connected + ArchipelagoConsole_SendMessage("[LOG] Connected."); + ArchipelagoClient::StartLocationScouts(); + + slotData = data; + + std::string expectedVersion = AP_Client_consts::AP_WORLD_VERSION; + std::string apworldVersion = slotData["apworld_version"]; + if (apworldVersion != expectedVersion) { + disconnecting = true; + std::string errorMessage = + "[ERROR] Client version does not match the APWorld's version.\nExpected version is " + expectedVersion + + ". APWorld is on version " + apworldVersion + + " instead.\nPlease use the SoH AP client matching the APWorld's version.\nDisconnecting..."; + ArchipelagoConsole_SendMessage(errorMessage.c_str()); + return; + } + + // if we are already in game when we connect + // we won't have to request an itemSynch + if (GameInteractor::IsSaveLoaded(true)) { + if (!isRightSaveLoaded()) { + disconnecting = true; + ArchipelagoConsole_SendMessage("[ERROR] Connected to incorrect slot, disconnecting..."); + return; + } + + // save the connection details in case they changed + SohUtils::CopyStringToCharArray(gSaveContext.ship.quest.data.archipelago.archiUri, uri, + ARRAY_COUNT(gSaveContext.ship.quest.data.archipelago.archiUri)); + SohUtils::CopyStringToCharArray(gSaveContext.ship.quest.data.archipelago.slotName, GetSlotName(), + ARRAY_COUNT(gSaveContext.ship.quest.data.archipelago.slotName)); + SohUtils::CopyStringToCharArray(gSaveContext.ship.quest.data.archipelago.roomPass, password, + ARRAY_COUNT(gSaveContext.ship.quest.data.archipelago.roomPass)); + + ResetQueue(); + SynchSentLocations(); + SynchReceivedLocations(); + } + }); + + apClient->set_slot_refused_handler([&](const std::list& msgs) { + disconnecting = true; + for (const std::string& msg : msgs) { + ArchipelagoConsole_SendMessage(std::string("[ERROR] " + msg).c_str()); + } + }); + + apClient->set_items_received_handler([&](const std::list& items) { + if (disconnecting) { + return; + } + + for (const APClient::NetworkItem& item : items) { + ApItem apItem; + const std::string game = apClient->get_player_game(item.player); + apItem.itemName = apClient->get_item_name(item.item, AP_Client_consts::AP_GAME_NAME); + apItem.locationName = apClient->get_location_name(item.location, game); + apItem.playerName = apClient->get_player_alias(item.player); + apItem.flags = item.flags; + apItem.index = item.index; + OnItemReceived(apItem); + } + }); + + apClient->set_location_info_handler([&](const std::list& items) { + if (disconnecting) { + return; + } + + scoutedItems.clear(); + + for (const APClient::NetworkItem& item : items) { + ApItem apItem; + const std::string game = apClient->get_player_game(item.player); + apItem.itemName = apClient->get_item_name(item.item, game); + apItem.locationName = apClient->get_location_name(item.location, AP_Client_consts::AP_GAME_NAME); + apItem.playerName = apClient->get_player_alias(item.player); + apItem.flags = item.flags; + apItem.index = item.index; + scoutedItems.push_back(apItem); + } + + CVarSetInteger(CVAR_REMOTE_ARCHIPELAGO("ConnectionStatus"), 4); // locations scouted + }); // todo maybe move these functions to a lambda, since they don't have to be static anymore + + apClient->set_location_checked_handler([&](const std::list locations) { + if (disconnecting) { + return; + } + + for (const int64_t apLoc : locations) { + QueueExternalCheck(apLoc); + } + }); + + apClient->set_print_json_handler([&](const APClient::PrintJSONArgs& arg) { + if (disconnecting) { + return; + } + + std::vector coloredNodes; + + for (const APClient::TextNode& node : arg.data) { + APClient* client = apClient.get(); + AP_Text::TextColor color = AP_Text::TextColor::COLOR_DEFAULT; + std::string text; + + if (node.type == "player_id") { + int id = std::stoi(node.text); + if (color == AP_Text::TextColor::COLOR_DEFAULT && id == client->get_player_number()) + color = AP_Text::TextColor::COLOR_MAGENTA; + else if (color == AP_Text::TextColor::COLOR_DEFAULT) + color = AP_Text::TextColor::COLOR_YELLOW; + text = client->get_player_alias(id); + } else if (node.type == "item_id") { + int64_t id = std::stoll(node.text); + if (color == AP_Text::TextColor::COLOR_DEFAULT) { + if (node.flags & APClient::ItemFlags::FLAG_ADVANCEMENT) + color = AP_Text::TextColor::COLOR_PLUM; + else if (node.flags & APClient::ItemFlags::FLAG_NEVER_EXCLUDE) + color = AP_Text::TextColor::COLOR_SLATEBLUE; + else if (node.flags & APClient::ItemFlags::FLAG_TRAP) + color = AP_Text::TextColor::COLOR_SALMON; + else + color = AP_Text::TextColor::COLOR_CYAN; + } + text = client->get_item_name(id, client->get_player_game(node.player)); + } else if (node.type == "location_id") { + int64_t id = std::stoll(node.text); + if (color == AP_Text::TextColor::COLOR_DEFAULT) + color = AP_Text::TextColor::COLOR_BLUE; + text = client->get_location_name(id, client->get_player_game(node.player)); + } else if (node.type == "hint_status") { + text = node.text; + if (node.hintStatus == APClient::HINT_FOUND) + color = AP_Text::TextColor::COLOR_GREEN; + else if (node.hintStatus == APClient::HINT_UNSPECIFIED) + color = AP_Text::TextColor::COLOR_GRAY; + else if (node.hintStatus == APClient::HINT_NO_PRIORITY) + color = AP_Text::TextColor::COLOR_SLATEBLUE; + else if (node.hintStatus == APClient::HINT_AVOID) + color = AP_Text::TextColor::COLOR_SALMON; + else if (node.hintStatus == APClient::HINT_PRIORITY) + color = AP_Text::TextColor::COLOR_PLUM; + else + color = AP_Text::TextColor::COLOR_RED; // unknown status -> red + } else if (node.type == "ERROR") { + color = AP_Text::TextColor::COLOR_ERROR; + text = node.text; + } else if (node.type == "LOG") { + color = AP_Text::TextColor::COLOR_LOG; + text = node.text; + } else { + color = AP_Text::TextColor::COLOR_WHITE; + text = node.text; + } + + AP_Text::ColoredTextNode Colornode; + Colornode.color = color; + Colornode.text = text; + coloredNodes.push_back(Colornode); + } + + ArchipelagoConsole_PrintJson(coloredNodes); + }); + + apClient->set_bounced_handler([&](const nlohmann::json data) { + if (data.contains("tags")) { + std::list tags = data["tags"]; + bool deathLink = (std::find(tags.begin(), tags.end(), "DeathLink") != tags.end()); + + if (deathLink && data["data"]["source"] != apClient->get_slot()) { + if (GameInteractor::IsSaveLoaded()) { + gSaveContext.health = 0; + std::string prefixText = std::string(data["data"]["source"]) + " died."; + Notification::Emit({ .prefix = prefixText, .message = "Cause:", .suffix = data["data"]["cause"] }); + std::string deathLinkMessage = "[LOG] Received death link from " + + std::string(data["data"]["source"]) + + ". Cause: " + std::string(data["data"]["cause"]); + ArchipelagoConsole_SendMessage(deathLinkMessage.c_str()); + + isDeathLinkedDeath = true; + } + } + } + }); + + return true; +} + +bool ArchipelagoClient::StopClient() { + disconnecting = true; + return true; +} + +void ArchipelagoClient::GameLoaded() { + + // Load textures for the archipelago items that're shown in the notifications + static bool sArchipelagoTexturesLoaded = false; + if (!sArchipelagoTexturesLoaded) { + Ship::Context::GetInstance()->GetWindow()->GetGui()->LoadTextureFromRawImage( + "Archipelago Progressive Icon", "textures/parameter_static/gArchipelagoProgressive.png"); + Ship::Context::GetInstance()->GetWindow()->GetGui()->LoadTextureFromRawImage( + "Archipelago Useful Icon", "textures/parameter_static/gArchipelagoUseful.png"); + Ship::Context::GetInstance()->GetWindow()->GetGui()->LoadTextureFromRawImage( + "Archipelago Junk Icon", "textures/parameter_static/gArchipelagoJunk.png"); + + sArchipelagoTexturesLoaded = true; + } + + if (apClient == nullptr) { + if (IS_ARCHIPELAGO) { + CVarSetString(CVAR_REMOTE_ARCHIPELAGO("ServerAddress"), gSaveContext.ship.quest.data.archipelago.archiUri); + CVarSetString(CVAR_REMOTE_ARCHIPELAGO("SlotName"), gSaveContext.ship.quest.data.archipelago.slotName); + CVarSetString(CVAR_REMOTE_ARCHIPELAGO("Password"), gSaveContext.ship.quest.data.archipelago.roomPass); + StartClient(); + } + return; + } + + // if its not an AP save, disconnect + if (!IS_ARCHIPELAGO) { + ArchipelagoConsole_SendMessage("[ERROR] Loaded save is not not an archipelago save, disconnecting..."); + disconnecting = true; + return; + } + + if (!isRightSaveLoaded()) { + ArchipelagoConsole_SendMessage("Disconnecting from previous slot and connecting to this one..."); + CVarSetString(CVAR_REMOTE_ARCHIPELAGO("ServerAddress"), gSaveContext.ship.quest.data.archipelago.archiUri); + CVarSetString(CVAR_REMOTE_ARCHIPELAGO("SlotName"), gSaveContext.ship.quest.data.archipelago.slotName); + CVarSetString(CVAR_REMOTE_ARCHIPELAGO("Password"), gSaveContext.ship.quest.data.archipelago.roomPass); + StartClient(); + return; + } + + SynchItems(); + SynchSentLocations(); + SynchReceivedLocations(); +} + +void ArchipelagoClient::StartLocationScouts() { + std::set missing_loc_set = apClient->get_missing_locations(); + std::set found_loc_set = apClient->get_checked_locations(); + std::list location_list; + for (const int64_t loc_id : missing_loc_set) { + location_list.emplace_back(loc_id); + } + for (const int64_t loc_id : found_loc_set) { + location_list.emplace_back(loc_id); + } + apClient->LocationScouts(location_list); +} + +void ArchipelagoClient::SynchItems() { + // Send a Synch request to get any items we may have missed + ResetQueue(); + apClient->Sync(); +} + +void ArchipelagoClient::SynchSentLocations() { + // send already checked locations + std::list checkedLocations; + for (const auto& loc : Rando::StaticData::GetLocationTable()) { + const RandomizerCheck rc = loc.GetRandomizerCheck(); + if (Rando::Context::GetInstance()->GetItemLocation(rc)->HasObtained()) { + const int64_t apLocation = apClient->get_location_id(loc.GetName()); + checkedLocations.emplace_back(apLocation); + } + } + + apClient->LocationChecks(checkedLocations); +} + +void ArchipelagoClient::SynchReceivedLocations() { + // Open checks that have been found previously but went unsaved + for (const int64_t apLoc : apClient->get_checked_locations()) { + QueueExternalCheck(apLoc); + } +} + +void ArchipelagoClient::QueueExternalCheck(const int64_t apLocation) { + const std::string checkName = apClient->get_location_name(apLocation, AP_Client_consts::AP_GAME_NAME); + const uint32_t RC = static_cast(Rando::StaticData::locationNameToEnum[checkName]); + + if (RC == RC_UNKNOWN_CHECK) { + ArchipelagoConsole_SendMessage("[ERROR] Attempting to queue an unknown location (RC_UNKOWN_CHECK), skipping."); + return; + } + + // Don't queue checks we already have + if (Rando::Context::GetInstance()->GetItemLocation(RC)->HasObtained()) { + return; + } + + GameInteractor_ExecuteOnRandomizerExternalCheck(RC); +} + +bool ArchipelagoClient::IsConnected() { + if (apClient == nullptr) { + return false; + } + + CVarSetInteger(CVAR_REMOTE_ANCHOR("RoomSettings.SyncItemsAndFlags"), 0); + if (Anchor::Instance->isConnected && Anchor::Instance->roomState.ownerClientId == Anchor::Instance->ownClientId) { + Anchor::Instance->SendPacket_UpdateRoomState(); + } + + return apClient->get_state() == APClient::State::SLOT_CONNECTED; +} + +void ArchipelagoClient::CheckLocation(RandomizerCheck sohCheckId) { + if (sohCheckId == RC_UNKNOWN_CHECK) { + ArchipelagoConsole_SendMessage("[ERROR] Trying to queue an unknown location (RC_UNKOWN_CHECK), skipping"); + return; + } + + if (!IsConnected()) { + return; + } + + std::string apName = Rando::StaticData::GetLocation(sohCheckId)->GetName(); + if (apName.empty()) { + return; + } + + int64_t apItemId = apClient->get_location_id(std::string(apName)); + apClient->LocationChecks({ apItemId }); +} + +void ArchipelagoClient::OnItemReceived(const ApItem apItem) { + + // Don't queue up any items when we aren't in game + // Any Items missed this way will get synched when we load the save + if (!GameInteractor::IsSaveLoaded(true)) { + return; + } + + // Skip queueing any items we already have + if (apItem.index < gSaveContext.ship.quest.data.archipelago.lastReceivedItemIndex) { + return; + } + + // Add item to the queue + receiveQueue.push(apItem); +} + +void ArchipelagoClient::QueueItem(const ApItem item) { + RandomizerGet RG = Rando::StaticData::itemNameToEnum[item.itemName]; + if (RG == RG_NONE) { + return; + } + + if (OTRGlobals::Instance->gRandomizer->GetItemObtainabilityFromRandomizerGet(RG) != CAN_OBTAIN) { + RG = RG_BLUE_RUPEE; + } + + itemQueued = true; + GameInteractor_ExecuteOnArchipelagoItemReceived(static_cast(RG)); +} + +void ArchipelagoClient::SendGameWon() { + if (apClient == nullptr) { + return; + } + + if (!gameWon) { + apClient->StatusUpdate(APClient::ClientStatus::GOAL); + gameWon = true; + } +} + +void ArchipelagoClient::SendMessageToConsole(const std::string message) { + // local commands not implemented yet + if (message.starts_with("/")) { + ArchipelagoConsole_SendMessage( + "Ship of Harkinian does not have any local commands.\nUse \"!help\" to see server commands instead."); + return; + } + + if (apClient == nullptr) { + ArchipelagoConsole_SendMessage("[ERROR] Could not send message. Please Connect to your slot."); + return; + } + + apClient->Say(message); +} + +void ArchipelagoClient::Poll() { + if (apClient == nullptr) { + return; + } + + if (disconnecting) { + apClient->reset(); + apClient = nullptr; + ResetQueue(); + disconnecting = false; + CVarSetInteger(CVAR_REMOTE_ARCHIPELAGO("ConnectionStatus"), 0); // disconnected + return; + } + + // queue another item to be received + if (!itemQueued && receiveQueue.size() > 0) { + const ApItem item = receiveQueue.front(); + receiveQueue.pop(); + QueueItem(item); + } + + apClient->poll(); +} + +void ArchipelagoClient::ResetQueue() { + itemQueued = false; + std::queue empty; + std::swap(receiveQueue, empty); +} + +bool ArchipelagoClient::slotMatch(const std::string& slotName, const std::string& roomHash) { + if (apClient == nullptr) { + return false; + } + + if (disconnecting) { + return false; + } + + const std::string seed = apClient->get_seed(); + const std::string slot = GetSlotName(); + + const bool seedMatch = apClient->get_seed().compare(roomHash) == 0; + const bool slotMatch = GetSlotName().compare(slotName) == 0; + return seedMatch && slotMatch; +} + +bool ArchipelagoClient::isRightSaveLoaded() const { + const bool seedMatch = apClient->get_seed().compare(gSaveContext.ship.quest.data.archipelago.roomHash) == 0; + const bool slotMatch = GetSlotName().compare(gSaveContext.ship.quest.data.archipelago.slotName) == 0; + return seedMatch && slotMatch; +} + +const std::string ArchipelagoClient::GetSlotName() const { + if (apClient == nullptr) { + return ""; + } + + return apClient->get_slot(); +} + +const nlohmann::json ArchipelagoClient::GetSlotData() { + return slotData; +} + +const std::vector& ArchipelagoClient::GetScoutedItems() { + return scoutedItems; +} + +uint8_t ArchipelagoClient::GetConnectionStatus() { + if (!apClient) { + return (uint8_t)APClient::State::DISCONNECTED; + } else { + return (uint8_t)apClient->get_state(); + } +} + +void ArchipelagoClient::OnItemGiven(uint32_t rc, GetItemEntry gi, uint8_t isGiSkipped) { + if (rc == RC_ARCHIPELAGO_RECEIVED_ITEM) { + gSaveContext.ship.quest.data.archipelago.lastReceivedItemIndex++; + ArchipelagoClient::GetInstance().itemQueued = false; + } else { + ArchipelagoClient::GetInstance().CheckLocation((RandomizerCheck)rc); + + if (isGiSkipped && gi.modIndex == MOD_RANDOMIZER && + (gi.getItemId == RG_ARCHIPELAGO_ITEM_PROGRESSIVE || gi.getItemId == RG_ARCHIPELAGO_ITEM_USEFUL || + gi.getItemId == RG_ARCHIPELAGO_ITEM_JUNK)) { + + const char* itemIcon = ""; + switch (gi.getItemId) { + case RG_ARCHIPELAGO_ITEM_PROGRESSIVE: + itemIcon = "Archipelago Progressive Icon"; + break; + case RG_ARCHIPELAGO_ITEM_USEFUL: + itemIcon = "Archipelago Useful Icon"; + break; + case RG_ARCHIPELAGO_ITEM_JUNK: + itemIcon = "Archipelago Junk Icon"; + break; + } + + std::string itemName = std::string(gSaveContext.ship.quest.data.archipelago.locations[rc].itemName); + + if (CVarGetInteger("gHoliday.Visual.HolidayPieces", 0)) { + + if (itemName == "Triforce Piece") { + itemName = "a Christmas Ornament"; + } + } + + Notification::Emit( + { .itemIcon = itemIcon, + .prefix = itemName, + .message = " for ", + .suffix = std::string(gSaveContext.ship.quest.data.archipelago.locations[rc].playerName) }); + } + } +} + +void ArchipelagoClient::SendDeathLink() { + if (apClient != nullptr && CVarGetInteger(CVAR_REMOTE_ARCHIPELAGO("DeathLink"), 0) && !isDeathLinkedDeath) { + nlohmann::json data{ { "time", apClient->get_server_time() }, + { "cause", "Shipwrecked by King Harkinian." }, + { "source", apClient->get_slot() } }; + apClient->Bounce(data, {}, {}, { "DeathLink" }); + + Notification::Emit({ .message = "Sending Death Link" }); + ArchipelagoConsole_SendMessage("[LOG] Died, sending death link."); + } + + isDeathLinkedDeath = false; +} + +void ArchipelagoClient::SetDeathLinkTag() { + if (!ArchipelagoClient::IsConnected()) { + return; + } + std::list tags; + if (CVarGetInteger(CVAR_REMOTE_ARCHIPELAGO("DeathLink"), 0)) { + tags.push_back("DeathLink"); + } + apClient->ConnectUpdate(false, 1, true, tags); +} + +std::vector archipelagoIceTrapModels = { + RG_MIRROR_SHIELD, + RG_BOOMERANG, + RG_LENS_OF_TRUTH, + RG_MEGATON_HAMMER, + RG_IRON_BOOTS, + RG_HOVER_BOOTS, + RG_STONE_OF_AGONY, + RG_DINS_FIRE, + RG_FARORES_WIND, + RG_NAYRUS_LOVE, + RG_FIRE_ARROWS, + RG_ICE_ARROWS, + RG_LIGHT_ARROWS, + RG_DOUBLE_DEFENSE, + RG_CLAIM_CHECK, + RG_PROGRESSIVE_HOOKSHOT, + RG_PROGRESSIVE_STRENGTH, + RG_PROGRESSIVE_BOMB_BAG, + RG_PROGRESSIVE_BOW, + RG_PROGRESSIVE_SLINGSHOT, + RG_PROGRESSIVE_WALLET, + RG_PROGRESSIVE_SCALE, + RG_PROGRESSIVE_MAGIC_METER, +}; + +RandomizerGet ArchipelagoClient::GetIceTrapItem() { + return RandomElement(archipelagoIceTrapModels); +} + +extern "C" void Archipelago_InitSaveFile() { + gSaveContext.ship.quest.data.archipelago.isArchipelago = 1; + + nlohmann::json slotData = ArchipelagoClient::GetInstance().GetSlotData(); + + std::vector scoutedItems = ArchipelagoClient::GetInstance().GetScoutedItems(); + + ArchipelagoClient& client = ArchipelagoClient::GetInstance(); + SohUtils::CopyStringToCharArray(gSaveContext.ship.quest.data.archipelago.roomHash, client.apClient->get_seed(), + ARRAY_COUNT(gSaveContext.ship.quest.data.archipelago.roomHash)); + SohUtils::CopyStringToCharArray(gSaveContext.ship.quest.data.archipelago.slotName, client.apClient->get_slot(), + ARRAY_COUNT(gSaveContext.ship.quest.data.archipelago.slotName)); + SohUtils::CopyStringToCharArray(gSaveContext.ship.quest.data.archipelago.archiUri, client.uri, + ARRAY_COUNT(gSaveContext.ship.quest.data.archipelago.archiUri)); + SohUtils::CopyStringToCharArray(gSaveContext.ship.quest.data.archipelago.roomPass, client.password, + ARRAY_COUNT(gSaveContext.ship.quest.data.archipelago.roomPass)); + + for (uint32_t i = 0; i < scoutedItems.size(); i++) { + RandomizerCheck rc = Rando::StaticData::locationNameToEnum[scoutedItems[i].locationName]; + + SohUtils::CopyStringToCharArray(gSaveContext.ship.quest.data.archipelago.locations[rc].itemName, + scoutedItems[i].itemName, + ARRAY_COUNT(gSaveContext.ship.quest.data.archipelago.locations[rc].itemName)); + SohUtils::CopyStringToCharArray(gSaveContext.ship.quest.data.archipelago.locations[rc].playerName, + scoutedItems[i].playerName, + ARRAY_COUNT(gSaveContext.ship.quest.data.archipelago.locations[rc].playerName)); + } +} + +void LoadArchipelagoData() { + SaveManager::Instance->LoadData("isArchipelago", gSaveContext.ship.quest.data.archipelago.isArchipelago); + SaveManager::Instance->LoadData("lastReceivedItemIndex", + gSaveContext.ship.quest.data.archipelago.lastReceivedItemIndex); + + SaveManager::Instance->LoadCharArray("roomHash", gSaveContext.ship.quest.data.archipelago.roomHash, + ARRAY_COUNT(gSaveContext.ship.quest.data.archipelago.roomHash)); + SaveManager::Instance->LoadCharArray("slotName", gSaveContext.ship.quest.data.archipelago.slotName, + ARRAY_COUNT(gSaveContext.ship.quest.data.archipelago.slotName)); + SaveManager::Instance->LoadCharArray("archiUri", gSaveContext.ship.quest.data.archipelago.archiUri, + ARRAY_COUNT(gSaveContext.ship.quest.data.archipelago.archiUri)); + SaveManager::Instance->LoadCharArray("roomPass", gSaveContext.ship.quest.data.archipelago.roomPass, + ARRAY_COUNT(gSaveContext.ship.quest.data.archipelago.roomPass)); + + SaveManager::Instance->LoadArray( + "locations", ARRAY_COUNT(gSaveContext.ship.quest.data.archipelago.locations), [](size_t i) { + SaveManager::Instance->LoadStruct("", [&i]() { + SaveManager::Instance->LoadCharArray( + "itemName", gSaveContext.ship.quest.data.archipelago.locations[i].itemName, + ARRAY_COUNT(gSaveContext.ship.quest.data.archipelago.locations[i].itemName)); + SaveManager::Instance->LoadCharArray( + "playerName", gSaveContext.ship.quest.data.archipelago.locations[i].playerName, + ARRAY_COUNT(gSaveContext.ship.quest.data.archipelago.locations[i].playerName)); + }); + }); +} + +void SaveArchipelagoData(SaveContext* saveContext, int sectionID, bool fullSave) { + SaveManager::Instance->SaveData("isArchipelago", saveContext->ship.quest.data.archipelago.isArchipelago); + SaveManager::Instance->SaveData("lastReceivedItemIndex", + saveContext->ship.quest.data.archipelago.lastReceivedItemIndex); + + SaveManager::Instance->SaveData("roomHash", saveContext->ship.quest.data.archipelago.roomHash); + SaveManager::Instance->SaveData("slotName", saveContext->ship.quest.data.archipelago.slotName); + SaveManager::Instance->SaveData("archiUri", saveContext->ship.quest.data.archipelago.archiUri); + SaveManager::Instance->SaveData("roomPass", gSaveContext.ship.quest.data.archipelago.roomPass); + + SaveManager::Instance->SaveArray( + "locations", ARRAY_COUNT(saveContext->ship.quest.data.archipelago.locations), [&](size_t i) { + SaveManager::Instance->SaveStruct("", [&]() { + SaveManager::Instance->SaveData("itemName", + saveContext->ship.quest.data.archipelago.locations[i].itemName); + SaveManager::Instance->SaveData("playerName", + saveContext->ship.quest.data.archipelago.locations[i].playerName); + }); + }); +} + +void InitArchipelagoData(bool isDebug) { + gSaveContext.ship.quest.data.archipelago.isArchipelago = 0; + gSaveContext.ship.quest.data.archipelago.lastReceivedItemIndex = 0; + + SohUtils::CopyStringToCharArray(gSaveContext.ship.quest.data.archipelago.roomHash, "", + ARRAY_COUNT(gSaveContext.ship.quest.data.archipelago.roomHash)); + SohUtils::CopyStringToCharArray(gSaveContext.ship.quest.data.archipelago.slotName, "", + ARRAY_COUNT(gSaveContext.ship.quest.data.archipelago.slotName)); + SohUtils::CopyStringToCharArray(gSaveContext.ship.quest.data.archipelago.archiUri, "", + ARRAY_COUNT(gSaveContext.ship.quest.data.archipelago.archiUri)); + SohUtils::CopyStringToCharArray(gSaveContext.ship.quest.data.archipelago.roomPass, "", + ARRAY_COUNT(gSaveContext.ship.quest.data.archipelago.roomPass)); + + for (uint32_t i = 0; i < ARRAY_COUNT(gSaveContext.ship.quest.data.archipelago.locations); i++) { + SohUtils::CopyStringToCharArray(gSaveContext.ship.quest.data.archipelago.locations[i].itemName, "", + ARRAY_COUNT(gSaveContext.ship.quest.data.archipelago.locations[i].itemName)); + SohUtils::CopyStringToCharArray(gSaveContext.ship.quest.data.archipelago.locations[i].playerName, "", + ARRAY_COUNT(gSaveContext.ship.quest.data.archipelago.locations[i].playerName)); + } +} + +void RegisterArchipelago() { + // make sure the client is constructed + ArchipelagoClient::GetInstance(); + + COND_HOOK(GameInteractor::OnGameFrameUpdate, true, []() { ArchipelagoClient::GetInstance().Poll(); }); + + COND_HOOK(GameInteractor::PostLoadGame, true, + [](int32_t file_id) { ArchipelagoClient::GetInstance().GameLoaded(); }); + + COND_HOOK(GameInteractor::OnRandomizerItemGivenHooks, IS_ARCHIPELAGO, + [](uint32_t rc, GetItemEntry gi, uint8_t isGiSkipped) { + ArchipelagoClient::GetInstance().OnItemGiven(rc, gi, isGiSkipped); + }); + + COND_HOOK(GameInteractor::OnPlayerDeath, IS_ARCHIPELAGO, + []() { ArchipelagoClient::GetInstance().SendDeathLink(); }); +} + +static RegisterShipInitFunc initFunc(RegisterArchipelago, { "IS_ARCHIPELAGO" }); diff --git a/soh/soh/Network/Archipelago/Archipelago.h b/soh/soh/Network/Archipelago/Archipelago.h new file mode 100644 index 00000000000..5b119fead49 --- /dev/null +++ b/soh/soh/Network/Archipelago/Archipelago.h @@ -0,0 +1,107 @@ +#pragma once +#ifdef __cplusplus +#include "soh/Enhancements/randomizer/randomizerTypes.h" +#include "soh/Enhancements/randomizer/static_data.h" +#include +#include +#include +#include "ArchipelagoTypes.h" + +// Forward declaration +class APClient; + +namespace AP_Client_consts { +static constexpr int MAX_ADDRESS_LENGTH = 64; +static constexpr int MAX_PLAYER_NAME_LENGHT = 17; +static constexpr int MAX_PASSWORD_LENGTH = 32; + +static constexpr char const* AP_GAME_NAME = "Ship of Harkinian"; +static constexpr char const* AP_WORLD_VERSION = "1.0.0"; +static constexpr int MAX_RETRIES = 3; +} // namespace AP_Client_consts + +class ArchipelagoClient { + public: + struct ApItem { + std::string itemName; + std::string locationName; + std::string playerName; + unsigned int flags; + uint64_t index; + }; + + static ArchipelagoClient& GetInstance(); + + bool StartClient(); + bool StopClient(); + + void GameLoaded(); + void StartLocationScouts(); + void SynchItems(); + void SynchSentLocations(); + void SynchReceivedLocations(); + + // getters + const std::string GetSlotName() const; + + uint8_t GetConnectionStatus(); + void OnItemGiven(uint32_t rc, GetItemEntry gi, uint8_t isGiSkipped); + void SendDeathLink(); + void SetDeathLinkTag(); + RandomizerGet GetIceTrapItem(); + const nlohmann::json GetSlotData(); + const std::vector& GetScoutedItems(); + + bool IsConnected(); + void CheckLocation(RandomizerCheck SoH_check_id); + + void OnItemReceived(const ApItem apItem); + void QueueItem(const ApItem item); + void QueueExternalCheck(int64_t apLocation); + + void SendGameWon(); + void SendMessageToConsole(const std::string message); + void Poll(); + void ResetQueue(); + + bool slotMatch(const std::string& slotName, const std::string& roomHash); + + std::unique_ptr apClient; + bool itemQueued; + bool disconnecting; + bool isDeathLinkedDeath; + int retries; + std::string uri; + std::string password; + + protected: + ArchipelagoClient(); + + private: + ArchipelagoClient(ArchipelagoClient&) = delete; + void operator=(const ArchipelagoClient&) = delete; + + bool isRightSaveLoaded() const; + + std::string uuid; + + static std::shared_ptr instance; + static bool initialized; + + bool gameWon; + + nlohmann::json slotData; + std::set locations; + std::vector scoutedItems; + std::queue receiveQueue; +}; + +void LoadArchipelagoData(); +void SaveArchipelagoData(SaveContext* saveContext, int sectionID, bool fullSave); +void InitArchipelagoData(bool isDebug); +extern "C" { +#endif // END __cplusplus +void Archipelago_InitSaveFile(); +#ifdef __cplusplus +} +#endif diff --git a/soh/soh/Network/Archipelago/ArchipelagoConsoleWindow.cpp b/soh/soh/Network/Archipelago/ArchipelagoConsoleWindow.cpp new file mode 100644 index 00000000000..48275e34fe7 --- /dev/null +++ b/soh/soh/Network/Archipelago/ArchipelagoConsoleWindow.cpp @@ -0,0 +1,139 @@ +#include "ArchipelagoConsoleWindow.h" + +#include "soh/SohGui/UIWidgets.hpp" +#include "soh/SohGui/SohGui.hpp" +#include "soh/OTRGlobals.h" +#include "ArchipelagoTypes.h" + +std::vector> Items; +bool autoScroll = true; + +using namespace UIWidgets; + +void ArchipelagoConsole_SendMessage(const char* fmt, ...) { + char buf[1024]; + va_list args; + va_start(args, fmt); + vsnprintf(buf, IM_ARRAYSIZE(buf), fmt, args); + buf[IM_ARRAYSIZE(buf) - 1] = 0; + va_end(args); + AP_Text::ColoredTextNode node; + node.text = std::string(buf); + node.color = AP_Text::TextColor::COLOR_WHITE; + if (strstr(buf, "[ERROR]")) { + node.color = AP_Text::TextColor::COLOR_ERROR; + } else if (strstr(buf, "[LOG]")) { + node.color = AP_Text::TextColor::COLOR_LOG; + } + std::vector line; + line.push_back(node); + Items.push_back(line); + if (Items.size() > 50) { + Items.erase(Items.begin()); + } +} + +void ArchipelagoConsole_PrintJson(const std::vector nodes) { + Items.push_back(nodes); + if (Items.size() > 50) { + Items.erase(Items.begin()); + } +} + +void ArchipelagoConsoleWindow::DrawElement() { + ImGui::SeparatorText("Archipelago Log"); + + ImGui::PushStyleColor(ImGuiCol_ChildBg, ImVec4(0.15f, 0.15f, 0.15f, 1.0f)); + ImGui::PushStyleVar(ImGuiStyleVar_ChildRounding, 8.0f); + ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(15.0f, 12.0f)); + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0.0f, 1.0f)); + + if (ImGui::BeginChild("ScrollingRegion", ImVec2(0, 400), ImGuiChildFlags_AlwaysUseWindowPadding, + ImGuiWindowFlags_HorizontalScrollbar)) { + + for (const std::vector& line : Items) { + for (const AP_Text::ColoredTextNode& node : line) { + ImGui::PushStyleColor(ImGuiCol_Text, getColorVal(node.color)); + ImGui::TextUnformatted(node.text.c_str()); + ImGui::SameLine(); + ImGui::PopStyleColor(); + } + ImGui::NewLine(); + } + + // Keep up at the bottom of the scroll region if we were already at the bottom at the beginning of the frame. + // Using a scrollbar or mouse-wheel will take away from the bottom edge. + if (autoScroll && ImGui::GetScrollY() >= ImGui::GetScrollMaxY()) { + ImGui::SetScrollHereY(1.0f); + } + } + ImGui::EndChild(); + ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 8.0f); + + static char textEntryBuf[1024]; + static bool keepFocus = false; + + if (keepFocus) { + ImGui::SetKeyboardFocusHere(); + keepFocus = false; + } + + PushStyleInput(THEME_COLOR); + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(10.0f, 8.0f)); + if (ImGui::InputText("##AP_MessageField", textEntryBuf, 1023, ImGuiInputTextFlags_EnterReturnsTrue)) { + ArchipelagoClient::GetInstance().SendMessageToConsole(std::string(textEntryBuf)); + textEntryBuf[0] = '\0'; + keepFocus = true; + } + ImGui::PopStyleVar(); + PopStyleInput(); + + ImGui::SameLine(); + + if (UIWidgets::Button("Send", UIWidgets::ButtonOptions().Color(THEME_COLOR).Size(ImVec2(0.0, 0.0)))) { + ArchipelagoClient::GetInstance().SendMessageToConsole(std::string(textEntryBuf)); + textEntryBuf[0] = '\0'; + keepFocus = true; + } + + ImGui::PopStyleColor(); + ImGui::PopStyleVar(4); +}; + +ImVec4 ArchipelagoConsoleWindow::getColorVal(const AP_Text::TextColor color) { + using apt = AP_Text::TextColor; + switch (color) { + case apt::COLOR_ERROR: + return ImVec4(1.0f, 0.4f, 0.4f, 1.0f); + case apt::COLOR_LOG: + return ImVec4(0.7f, 0.7f, 1.0f, 1.0f); + case apt::COLOR_BLACK: + return ImVec4(0.000f, 0.000f, 0.000f, 1.00f); + case apt::COLOR_RED: + return ImVec4(0.933f, 0.000f, 0.000f, 1.00f); + case apt::COLOR_GREEN: + return ImVec4(0.000f, 1.000f, 0.498f, 1.00f); + case apt::COLOR_YELLOW: + return ImVec4(0.980f, 0.980f, 0.824f, 1.00f); + case apt::COLOR_BLUE: + return ImVec4(0.392f, 0.584f, 0.929f, 1.00f); + case apt::COLOR_CYAN: + return ImVec4(0.000f, 0.933f, 0.933f, 1.00f); + case apt::COLOR_MAGENTA: + return ImVec4(0.933f, 0.000f, 0.933f, 1.00f); + case apt::COLOR_SLATEBLUE: + return ImVec4(0.427f, 0.545f, 0.910f, 1.00f); + case apt::COLOR_PLUM: + return ImVec4(0.686f, 0.600f, 0.937f, 1.00f); + case apt::COLOR_SALMON: + return ImVec4(0.980f, 0.502f, 0.447f, 1.00f); + case apt::COLOR_ORANGE: + return ImVec4(1.000, 0.467f, 0.000f, 1.000f); + case apt::COLOR_GRAY: + return ImVec4(0.53f, 0.53f, 0.53f, 1.00f); + case apt::COLOR_WHITE: + case apt::COLOR_DEFAULT: + default: + return ImVec4(0.93f, 0.93f, 0.93f, 1.00f); + }; +} diff --git a/soh/soh/Network/Archipelago/ArchipelagoConsoleWindow.h b/soh/soh/Network/Archipelago/ArchipelagoConsoleWindow.h new file mode 100644 index 00000000000..310c324064c --- /dev/null +++ b/soh/soh/Network/Archipelago/ArchipelagoConsoleWindow.h @@ -0,0 +1,27 @@ +#pragma once +#ifndef ARCHIPELAGO_CONSOLE_WINDOW_H +#define ARCHIPELAGO_CONSOLE_WINDOW_H + +#include +#include +#include +#include "ship/window/gui/Gui.h" +#include "ArchipelagoTypes.h" + +class ArchipelagoConsoleWindow final : public Ship::GuiWindow { + public: + using GuiWindow::GuiWindow; + ~ArchipelagoConsoleWindow(){}; + + static ImVec4 getColorVal(const AP_Text::TextColor color); + + protected: + void InitElement() override{}; + void DrawElement() override; + void UpdateElement() override{}; +}; + +void ArchipelagoConsole_SendMessage(const char* fmt, ...); +void ArchipelagoConsole_PrintJson(const std::vector nodes); + +#endif // ARCHIPELAGO_CONSOLE_WINDOW_H \ No newline at end of file diff --git a/soh/soh/Network/Archipelago/ArchipelagoSettingsWindow.cpp b/soh/soh/Network/Archipelago/ArchipelagoSettingsWindow.cpp new file mode 100644 index 00000000000..e9f1a23713f --- /dev/null +++ b/soh/soh/Network/Archipelago/ArchipelagoSettingsWindow.cpp @@ -0,0 +1,91 @@ +#include "ArchipelagoSettingsWindow.h" +#include "Archipelago.h" + +#include "soh/SohGui/UIWidgets.hpp" +#include "soh/SohGui/SohGui.hpp" +#include "soh/Network/Archipelago/ArchipelagoConsoleWindow.h" +#include "soh/SaveManager.h" + +void ArchipelagoSettingsWindow::DrawElement() { + ArchipelagoClient& apClient = ArchipelagoClient::GetInstance(); + + ImGui::SeparatorText("Connection info"); + + UIWidgets::PushStyleCombobox(THEME_COLOR); + ImGui::PushStyleColor(ImGuiCol_Border, UIWidgets::ColorValues.at(THEME_COLOR)); + + ImGui::Text("Server Address"); + UIWidgets::CVarInputString("##ArchipelagoServerAddress", CVAR_REMOTE_ARCHIPELAGO("ServerAddress"), + UIWidgets::InputOptions() + .Color(THEME_COLOR) + .PlaceholderText("archipelago.gg:38281") + .DefaultValue("archipelago.gg:38281") + .Size(ImVec2(ImGui::GetFontSize() * 15, 0)) + .LabelPosition(UIWidgets::LabelPositions::None)); + ImGui::Text("Slot Name"); + UIWidgets::CVarInputString("##ArchipelagoSlotName", CVAR_REMOTE_ARCHIPELAGO("SlotName"), + UIWidgets::InputOptions() + .Color(THEME_COLOR) + .Size(ImVec2(ImGui::GetFontSize() * 15, 0)) + .LabelPosition(UIWidgets::LabelPositions::None)); + ImGui::Text("Password (leave blank for no password)"); + UIWidgets::CVarInputString("##ArchipelagoPassword", CVAR_REMOTE_ARCHIPELAGO("Password"), + UIWidgets::InputOptions() + .Color(THEME_COLOR) + .IsSecret(true) + .Size(ImVec2(ImGui::GetFontSize() * 15, 0)) + .LabelPosition(UIWidgets::LabelPositions::None)); + ImGui::PopStyleColor(); + UIWidgets::PopStyleCombobox(); + + if (!apClient.IsConnected()) { + if (UIWidgets::Button("Connect", UIWidgets::ButtonOptions().Color(THEME_COLOR).Size(ImVec2(0.0, 0.0)))) { + bool success = apClient.StartClient(); + } + } else { + if (UIWidgets::Button("Disconnect", UIWidgets::ButtonOptions().Color(THEME_COLOR).Size(ImVec2(0.0, 0.0)))) { + bool success = apClient.StopClient(); + } + } + + ImGui::SameLine(); + + uint8_t clientStatus = apClient.GetConnectionStatus(); + switch (clientStatus) { + case 1: + case 2: + case 3: + ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.7f, 0.7f, 0.7f, 1.0f)); + ImGui::Text("Connecting..."); + break; + case 4: + ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.5f, 1.0f, 0.5f, 1.0f)); + ImGui::Text("Connected"); + break; + default: + ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 0.5f, 0.5f, 1.0f)); + ImGui::Text("Not Connected"); + break; + } + ImGui::PopStyleColor(); + + ImGui::SeparatorText("Additional Options"); + if (UIWidgets::CVarCheckbox( + "Death Link", CVAR_REMOTE_ARCHIPELAGO("DeathLink"), + UIWidgets::CheckboxOptions().Color(THEME_COLOR).Tooltip("You die, others die. Others die, you die!"))) { + apClient.SetDeathLinkTag(); + } + UIWidgets::CVarCheckbox( + "Shuffle Entrances", CVAR_REMOTE_ARCHIPELAGO("ShuffleEntrances"), + UIWidgets::CheckboxOptions() + .Color(THEME_COLOR) + .Tooltip( + "With this checked, pull in a spoiler with entrances shuffled and create a new AP save and your " + "entrances will match the spoiler. Uncheck and start a new save file to unshuffle entrances again.")); +}; + +void ArchipelagoSettingsWindow::InitElement() { + SaveManager::Instance->AddLoadFunction("archipelagoData", 1, LoadArchipelagoData); + SaveManager::Instance->AddSaveFunction("archipelagoData", 1, SaveArchipelagoData, true, SECTION_PARENT_NONE); + SaveManager::Instance->AddInitFunction(InitArchipelagoData); +} diff --git a/soh/soh/Network/Archipelago/ArchipelagoSettingsWindow.h b/soh/soh/Network/Archipelago/ArchipelagoSettingsWindow.h new file mode 100644 index 00000000000..cc71e6fc2a0 --- /dev/null +++ b/soh/soh/Network/Archipelago/ArchipelagoSettingsWindow.h @@ -0,0 +1,19 @@ +#pragma once +#ifndef ARCHIPELAGO_SETTINGS_WINDOW_H +#define ARCHIPELAGO_SETTINGS_WINDOW_H + +#include +#include "ship/window/gui/Gui.h" + +class ArchipelagoSettingsWindow final : public Ship::GuiWindow { + public: + using GuiWindow::GuiWindow; + ~ArchipelagoSettingsWindow(){}; + + protected: + void InitElement() override; + void DrawElement() override; + void UpdateElement() override{}; +}; + +#endif // ARCHIPELAGO_SETTINGS_WINDOW_H \ No newline at end of file diff --git a/soh/soh/Network/Archipelago/ArchipelagoTypes.h b/soh/soh/Network/Archipelago/ArchipelagoTypes.h new file mode 100644 index 00000000000..feaf8999a1a --- /dev/null +++ b/soh/soh/Network/Archipelago/ArchipelagoTypes.h @@ -0,0 +1,30 @@ +#pragma once +#ifdef __cplusplus + +namespace AP_Text { +enum class TextColor : char { + COLOR_DEFAULT = 0, + COLOR_ERROR, + COLOR_LOG, + COLOR_BLACK, + COLOR_RED, + COLOR_GREEN, + COLOR_YELLOW, + COLOR_BLUE, + COLOR_CYAN, + COLOR_MAGENTA, + COLOR_SLATEBLUE, + COLOR_PLUM, + COLOR_SALMON, + COLOR_WHITE, + COLOR_ORANGE, + COLOR_GRAY +}; + +struct ColoredTextNode { + std::string text; + AP_Text::TextColor color; +}; +} // namespace AP_Text + +#endif \ No newline at end of file diff --git a/soh/soh/Network/Archipelago/README.md b/soh/soh/Network/Archipelago/README.md new file mode 100644 index 00000000000..f330f9a9f53 --- /dev/null +++ b/soh/soh/Network/Archipelago/README.md @@ -0,0 +1,19 @@ +# Archipelago Integration + +## todo +document stuff + +## Cert stores and `.pem` files + +[`apclientpp` suggests](https://github.com/black-sliver/apclientpp?tab=readme-ov-file#ssl-support) +> * to make certificate verifaction work cross-platform +> * include a cert store file and its license, e.g. [curl's CA Extract](https://curl.se/docs/caextract.html) +> * load the cert store by passing the path as 4th argument to APClient's constructor +> * apclient will try to load system certs, but this should only be used for testing: outdated Windows has outdated certs, macos/Linux without OpenSSL or with a different version won't find any certs + +Connecting using system certs has seemed to work just fine, so for the initial implementation we've decided to temporarily go against the "this should only be used for testing" advice. + +An ideal implementation would: +* Use `libcurl` to download a cert from CA Extract at runtime before initializing the `APClient` + * This would be done with the `libcurl` c-code equivalent of `curl --etag-compare etag.txt --etag-save etag.txt --remote-name https://curl.se/ca/cacert.pem` +* Use the downloaded cert when initializing `APClient` diff --git a/soh/soh/Network/CrowdControl/CrowdControl.cpp b/soh/soh/Network/CrowdControl/CrowdControl.cpp index 9709fc4523d..8a152a29138 100644 --- a/soh/soh/Network/CrowdControl/CrowdControl.cpp +++ b/soh/soh/Network/CrowdControl/CrowdControl.cpp @@ -6,7 +6,16 @@ #include #include #include +#include +#include +#include +#include +#include + #include "soh/OTRGlobals.h" +#include "soh/Notification/Notification.h" +#include "soh/Enhancements/game-interactor/GameInteractor.h" +#include "soh/SohGui/SohGui.hpp" extern "C" { #include @@ -16,19 +25,141 @@ extern "C" { extern PlayState* gPlayState; } +static uint64_t NowMs() { + using namespace std::chrono; + return (uint64_t)duration_cast(steady_clock::now().time_since_epoch()).count(); +} + +static float sCCTimersDesiredWidth = 0.0f; + +static void CrowdControl_RegisterHooks(); + +void CrowdControl::MainThreadTick() { + PumpPendingRemovals(); + + SyncChaosStartup(); + + bool shouldRetryQueued = false; + { + std::scoped_lock lock(activeEffectsMutex); + for (auto* e : activeEffects) { + if (!e) { + continue; + } + if (e->runOnceWhenPossible) { + shouldRetryQueued = true; + break; + } + const bool chaosEnabled = (CVarGetInteger(CVAR_REMOTE_CROWD_CONTROL("ChaosEnabled"), 0) != 0); + + // NEW: if chaos is enabled and we have any active effects at all, + // make sure ProcessActiveEffectsOnce() gets a 1Hz main-thread chance to run. + if (chaosEnabled && !activeEffects.empty()) { + shouldRetryQueued = true; + break; + } + + // Existing: when chaos is off, but there are paused timed effects, + // we still need to retry them (at 1Hz) so they can start when possible. + if (!chaosEnabled && e->timeRemaining > 0 && e->isPaused) { + shouldRetryQueued = true; + break; + } + } + } + + if (shouldRetryQueued && gPlayState != nullptr) { + // IMPORTANT: don't run this every frame when Chaos is off, + // or started timed effects will "tick" 1000ms per frame. + static uint64_t sLastRetryMs = 0; + uint64_t now = NowMs(); + const uint64_t kRetryIntervalMs = 1000; + + if (now - sLastRetryMs >= kRetryIntervalMs) { + sLastRetryMs = now; + ProcessActiveEffectsOnce(); + } + } +} + +static void CrowdControl_OnGameFrameUpdate() { + if (CrowdControl::Instance == nullptr) { + return; + } + + // Always tick so pending removals can be pumped even during transitions. + CrowdControl::Instance->MainThreadTick(); +} + +static void CrowdControl_OnPlayDestroy() { + if (CrowdControl::Instance == nullptr) { + return; + } + + // Safe to flush pending removals during teardown + CrowdControl::Instance->MainThreadTick(); +} + +static void CrowdControl_RegisterHooks() { + static HOOK_ID sGameFrameHookID = 0; + static HOOK_ID sPlayDestroyHookID = 0; + static bool sRegistered = false; + + // Register hooks whenever chaos OR remote CC is enabled. + // (This keeps things consistent even if the menu is not visible.) + const bool chaosEnabled = (CVarGetInteger(CVAR_REMOTE_CROWD_CONTROL("ChaosEnabled"), 0) != 0); + const bool remoteEnabled = (CVarGetInteger(CVAR_REMOTE_CROWD_CONTROL("Enabled"), 0) != 0); + const bool shouldBeRegistered = + (chaosEnabled || remoteEnabled || (CrowdControl::Instance && CrowdControl::Instance->HasPendingWork())); + + if (shouldBeRegistered == sRegistered) { + return; + } + + GameInteractor::Instance->UnregisterGameHook(sGameFrameHookID); + GameInteractor::Instance->UnregisterGameHook(sPlayDestroyHookID); + sGameFrameHookID = 0; + sPlayDestroyHookID = 0; + sRegistered = false; + + if (!shouldBeRegistered) { + return; + } + + sRegistered = true; + + sGameFrameHookID = + GameInteractor::Instance->RegisterGameHook(CrowdControl_OnGameFrameUpdate); + + sPlayDestroyHookID = + GameInteractor::Instance->RegisterGameHook(CrowdControl_OnPlayDestroy); +} + void CrowdControl::Enable() { Network::Enable(CVarGetString(CVAR_REMOTE_CROWD_CONTROL("Host"), "127.0.0.1"), CVarGetInteger(CVAR_REMOTE_CROWD_CONTROL("Port"), 43384)); + + // Ensure main-thread hook is installed when remote CC is enabled. + CrowdControl_RegisterHooks(); } void CrowdControl::OnConnected() { ccThreadProcess = std::thread(&CrowdControl::ProcessActiveEffects, this); + + // In case someone flips cvars outside the UI, keep hooks in sync. + CrowdControl_RegisterHooks(); } void CrowdControl::OnDisconnected() { ccThreadProcess.join(); } +bool CrowdControl::HasPendingWork() { + // Used by hook registration to keep main-thread cleanup alive even when Chaos is toggled off. + std::scoped_lock lock(pendingRemovalsMutex, activeEffectsMutex); + return !pendingRemovals.empty() || !activeEffects.empty(); +} + void CrowdControl::OnIncomingJson(nlohmann::json payload) { Effect* incomingEffect = ParseMessage(payload); if (!incomingEffect) { @@ -39,33 +170,236 @@ void CrowdControl::OnIncomingJson(nlohmann::json payload) { if (!incomingEffect->timeRemaining) { EffectResult result = CrowdControl::ExecuteEffect(incomingEffect); EmitMessage(incomingEffect->id, incomingEffect->timeRemaining, result); + delete incomingEffect; } 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; + { + std::scoped_lock lock(activeEffectsMutex); + 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; + } } } - 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 (isConflictingEffectActive) { + delete incomingEffect; + return; + } + + // 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); + delete incomingEffect; + return; + } - activeEffectsMutex.lock(); + { + std::scoped_lock lock(activeEffectsMutex); activeEffects.push_back(incomingEffect); - activeEffectsMutex.unlock(); } } } +void CrowdControl::ProcessActiveEffectsOnce() { + std::scoped_lock lock(activeEffectsMutex); + + // When remote CC is connected, the background ProcessActiveEffects() thread owns NON-CHAOS timed effects. + // ProcessActiveEffectsOnce() should only handle queued one-shot effects and Chaos-owned timed effects. + const bool remoteThreadRunning = isConnected; + const bool chaosEnabled = (CVarGetInteger(CVAR_REMOTE_CROWD_CONTROL("ChaosEnabled"), 0) != 0); + + // Defer resuming a started Chaos effect for exactly one tick after it becomes unblocked, + // to avoid a 1-second "on -> off -> on" flicker when the ended remote effect's Remove() + // arrives slightly late. + static std::unordered_set sDeferResumeOnce; + + auto it = activeEffects.begin(); + while (it != activeEffects.end()) { + Effect* effect = *it; + if (!effect) { + it = activeEffects.erase(it); + continue; + } + + // If remote CC thread is running, do not interfere with non-Chaos timed effects at all. + if (remoteThreadRunning && effect->timeRemaining > 0 && !effect->isChaos) { + ++it; + continue; + } + + // If another older timed effect in the same category exists, keep this queued. + if (effect->timeRemaining > 0) { + bool blocked = false; + for (Effect* other : activeEffects) { + if (!other || other == effect) { + continue; + } + if (other->timeRemaining > 0 && other->category == effect->category && other->id < effect->id) { + blocked = true; + break; + } + } + + // If a previous effect in this category just ended and is waiting for main-thread removal, + // do not start the next one yet. Otherwise the late removal can undo the newly-started effect. + if (!blocked) { + std::scoped_lock rlock(pendingRemovalsMutex); + for (auto& [r, cat] : pendingRemovals) { + if (cat == effect->category) { + blocked = true; + break; + } + } + } + + if (blocked) { + // If it's blocked, clear any pending one-tick defer state so we don't "consume" it early. + sDeferResumeOnce.erase(effect->id); + + if (!effect->isPaused) { + effect->isPaused = true; + EmitMessage(effect->id, effect->timeRemaining, EffectResult::Paused); + } + ++it; + continue; + } + + // Not blocked. If we are about to resume a previously-started timed effect while the remote + // thread is running, defer exactly one tick so any late Remove() can land first. + if (remoteThreadRunning) { + const bool started = (effect->lastExecutionResult == EffectResult::Success); + + if (started && effect->isPaused) { + if (sDeferResumeOnce.find(effect->id) == sDeferResumeOnce.end()) { + sDeferResumeOnce.insert(effect->id); + ++it; + continue; + } else { + sDeferResumeOnce.erase(effect->id); + } + } else { + // Keep the set clean for non-resume cases. + sDeferResumeOnce.erase(effect->id); + } + } else { + // If remote thread isn't running, we don't need this guard. + sDeferResumeOnce.erase(effect->id); + } + } else { + // Non-timed effects shouldn't participate in this guard. + sDeferResumeOnce.erase(effect->id); + } + + // Queued non-timed effects: keep retrying until Success, then remove immediately. + // No ticking, no pause/resume semantics, and crucially: no re-firing multiple times. + if (effect->runOnceWhenPossible) { + EffectResult result = CrowdControl::ExecuteEffect(effect); + if (result == EffectResult::Success) { + it = activeEffects.erase(it); + delete effect; + continue; + } + // Still not possible: keep it queued, try again next tick. + ++it; + continue; + } + + // Timed effects below this point are Chaos-owned (or offline). + if (effect->timeRemaining > 0) { + const bool started = (effect->lastExecutionResult == EffectResult::Success); + + // If the timed effect has already started: + if ((chaosEnabled || effect->isChaos) && started) { + // IMPORTANT: keep Chaos-owned timed effects "alive" by executing them every tick, + // the same way the remote CC thread does. If another effect's Remove() undid the + // modifier, this re-applies it. + EffectResult r = CrowdControl::ExecuteEffect(effect); + + if (r == EffectResult::Success) { + if (effect->isPaused) { + effect->isPaused = false; + EmitMessage(effect->id, effect->timeRemaining, EffectResult::Resumed); + } + + // Only tick time when we successfully applied this tick. + effect->timeRemaining -= 1000; + + if (effect->timeRemaining <= 0) { + it = activeEffects.erase(it); + + if (auto* removable = dynamic_cast(effect->giEffect)) { + std::scoped_lock rlock(pendingRemovalsMutex); + pendingRemovals.push_back({ removable, effect->category }); + } + + delete effect; + continue; + } + + ++it; + continue; + } + + if (r == EffectResult::Retry) { + // Can't apply right now; pause and DO NOT tick time. + if (!effect->isPaused) { + effect->isPaused = true; + EmitMessage(effect->id, effect->timeRemaining, EffectResult::Paused); + } + ++it; + continue; + } + + // Failure: drop it. + it = activeEffects.erase(it); + delete effect; + continue; + } + + // Not started yet: attempt to start once. + EffectResult result = CrowdControl::ExecuteEffect(effect); + + if (result == EffectResult::Success) { + effect->lastExecutionResult = EffectResult::Success; + + // If we have a success after previously being paused, tell CC to resume timer. + if (effect->isPaused) { + effect->isPaused = false; + EmitMessage(effect->id, effect->timeRemaining, EffectResult::Resumed); + } else { + EmitMessage(effect->id, effect->timeRemaining, EffectResult::Success); + } + + ++it; + continue; + } + + if (result == EffectResult::Failure) { + it = activeEffects.erase(it); + delete effect; + continue; + } + + // Retry: stay queued/paused. + if (!effect->isPaused) { + effect->isPaused = true; + EmitMessage(effect->id, effect->timeRemaining, EffectResult::Paused); + } + ++it; + continue; + } + + // Nothing to do for zero-time effects that aren't runOnceWhenPossible. + ++it; + } +} + void CrowdControl::ProcessActiveEffects() { while (isEnabled) { // We only want to send events when status changes, on start we send Success. @@ -76,14 +410,25 @@ void CrowdControl::ProcessActiveEffects() { while (it != activeEffects.end()) { Effect* effect = *it; + + // NEW: Remote CC thread must NEVER drive Chaos-owned effects + if (effect && effect->isChaos) { + ++it; + continue; + } EffectResult result = CrowdControl::ExecuteEffect(effect); if (result == EffectResult::Success) { // If time remaining has reached 0, we have finished the effect. if (effect->timeRemaining <= 0) { - it = activeEffects.erase(std::remove(activeEffects.begin(), activeEffects.end(), effect), - activeEffects.end()); - GameInteractor::RemoveEffect(dynamic_cast(effect->giEffect)); + it = activeEffects.erase(it); + + // IMPORTANT: don't call RemoveEffect(nullptr). Enqueue main-thread removal if removable. + if (auto* removable = dynamic_cast(effect->giEffect)) { + std::scoped_lock rlock(pendingRemovalsMutex); + pendingRemovals.push_back({ removable, effect->category }); + } + delete effect; } else { // If we have a success after previously being paused, tell CC to resume timer. @@ -119,6 +464,10 @@ void CrowdControl::ProcessActiveEffects() { } void CrowdControl::EmitMessage(uint32_t eventId, long timeRemaining, EffectResult status) { + if (!isConnected) { + return; + } + nlohmann::json payload; payload["id"] = eventId; @@ -146,11 +495,16 @@ CrowdControl::EffectResult CrowdControl::ExecuteEffect(Effect* effect) { return TranslateGiEnum(giResult); } -/// Checks if effect can be applied -- should not be used to check for spawn enemy effects. +// Checks if effect can be applied -- should not be used to check for spawn enemy effects. CrowdControl::EffectResult CrowdControl::CanApplyEffect(Effect* effect) { - assert(effect->category != kEffectCatSpawnEnemy || effect->category != kEffectCatSpawnActor); - GameInteractionEffectQueryResult giResult = GameInteractor::CanApplyEffect(effect->giEffect); + // FIX: this was using || which makes it basically always true. + assert(effect->category != kEffectCatSpawnEnemy && effect->category != kEffectCatSpawnActor); + + if (gPlayState == nullptr || GET_PLAYER(gPlayState) == nullptr) { + return EffectResult::Retry; + } + GameInteractionEffectQueryResult giResult = GameInteractor::CanApplyEffect(effect->giEffect); return TranslateGiEnum(giResult); } @@ -168,6 +522,26 @@ CrowdControl::EffectResult CrowdControl::TranslateGiEnum(GameInteractionEffectQu return result; } +// Call this from MAIN THREAD once per frame (best), or at least whenever you know you're on main thread. +// Do NOT call from the chaos thread. +void CrowdControl::PumpPendingRemovals() { + if (gPlayState == nullptr) { + return; + } + + std::vector> local; + { + std::scoped_lock lock(pendingRemovalsMutex); + local.swap(pendingRemovals); + } + + for (auto& [r, cat] : local) { + if (r) { + GameInteractor::RemoveEffect(r); + } + } +} + CrowdControl::Effect* CrowdControl::ParseMessage(nlohmann::json dataReceived) { if (!dataReceived.contains("id") || !dataReceived.contains("type")) { SPDLOG_ERROR("[CrowdControl] Invalid payload received:\n{}", dataReceived.dump()); @@ -190,10 +564,26 @@ CrowdControl::Effect* CrowdControl::ParseMessage(nlohmann::json dataReceived) { uint32_t receivedParameter = 0; auto effectName = dataReceived["code"].get(); + // NEW: store code + default display name (can be upgraded to uiName later) + effect->effectCode = effectName; + effect->displayName = effectName; + if (parameters.size() > 0) { receivedParameter = dataReceived["parameters"][0]; } + // ------------------------------------------------------------------------- + // Duration CVAR helper (seconds -> ms), NO clamp (except negative -> 0) + // ------------------------------------------------------------------------- + auto GetDurationMs = [&](int defaultSeconds) -> long { + const std::string key = std::string(CVAR_REMOTE_CROWD_CONTROL("ChaosEffectDuration.")) + effectName; + int sec = CVarGetInteger(key.c_str(), defaultSeconds); + if (sec < 0) { + sec = 0; + } + return (long)sec * 1000L; + }; + // Assign GameInteractionEffect + values to CC effect. // Categories are mostly used for checking for conflicting timed effects. switch (effectStringToEnum[effectName]) { @@ -278,82 +668,81 @@ CrowdControl::Effect* CrowdControl::ParseMessage(nlohmann::json dataReceived) { // Link Modifiers case kEffectTakeHalfDamage: effect->category = kEffectCatDamageTaken; - effect->timeRemaining = 30000; + effect->timeRemaining = GetDurationMs(30); effect->giEffect = new GameInteractionEffect::ModifyDefenseModifier(); dynamic_cast(effect->giEffect)->parameters[0] = 2; break; case kEffectTakeDoubleDamage: effect->category = kEffectCatDamageTaken; - effect->timeRemaining = 30000; + effect->timeRemaining = GetDurationMs(30); effect->giEffect = new GameInteractionEffect::ModifyDefenseModifier(); dynamic_cast(effect->giEffect)->parameters[0] = -2; break; case kEffectOneHitKo: effect->category = kEffectCatDamageTaken; - effect->timeRemaining = 30000; + effect->timeRemaining = GetDurationMs(30); effect->giEffect = new GameInteractionEffect::OneHitKO(); break; case kEffectInvincibility: effect->category = kEffectCatDamageTaken; - effect->timeRemaining = 15000; + effect->timeRemaining = GetDurationMs(15); effect->giEffect = new GameInteractionEffect::PlayerInvincibility(); break; - break; case kEffectIncreaseSpeed: effect->category = kEffectCatSpeed; - effect->timeRemaining = 30000; + effect->timeRemaining = GetDurationMs(30); effect->giEffect = new GameInteractionEffect::ModifyMovementSpeedMultiplier(); dynamic_cast(effect->giEffect)->parameters[0] = 2; break; case kEffectDecreaseSpeed: effect->category = kEffectCatSpeed; - effect->timeRemaining = 30000; + effect->timeRemaining = GetDurationMs(30); effect->giEffect = new GameInteractionEffect::ModifyMovementSpeedMultiplier(); dynamic_cast(effect->giEffect)->parameters[0] = -2; break; case kEffectLowGravity: effect->category = kEffectCatGravity; - effect->timeRemaining = 30000; + effect->timeRemaining = GetDurationMs(30); effect->giEffect = new GameInteractionEffect::ModifyGravity(); dynamic_cast(effect->giEffect)->parameters[0] = GI_GRAVITY_LEVEL_LIGHT; break; case kEffectHighGravity: effect->category = kEffectCatGravity; - effect->timeRemaining = 30000; + effect->timeRemaining = GetDurationMs(30); effect->giEffect = new GameInteractionEffect::ModifyGravity(); dynamic_cast(effect->giEffect)->parameters[0] = GI_GRAVITY_LEVEL_HEAVY; break; case kEffectForceIronBoots: effect->category = kEffectCatBoots; - effect->timeRemaining = 30000; + effect->timeRemaining = GetDurationMs(30); effect->giEffect = new GameInteractionEffect::ForceEquipBoots(); dynamic_cast(effect->giEffect)->parameters[0] = EQUIP_VALUE_BOOTS_IRON; break; case kEffectForceHoverBoots: effect->category = kEffectCatBoots; - effect->timeRemaining = 30000; + effect->timeRemaining = GetDurationMs(30); effect->giEffect = new GameInteractionEffect::ForceEquipBoots(); dynamic_cast(effect->giEffect)->parameters[0] = EQUIP_VALUE_BOOTS_HOVER; break; case kEffectSlipperyFloor: effect->category = kEffectCatSlipperyFloor; - effect->timeRemaining = 30000; + effect->timeRemaining = GetDurationMs(30); effect->giEffect = new GameInteractionEffect::SlipperyFloor(); break; case kEffectNoLedgeGrabs: effect->category = kEffectCatNoLedgeGrabs; - effect->timeRemaining = 30000; + effect->timeRemaining = GetDurationMs(30); effect->giEffect = new GameInteractionEffect::DisableLedgeGrabs(); break; case kEffectRandomWind: effect->category = kEffectCatRandomWind; - effect->timeRemaining = 30000; + effect->timeRemaining = GetDurationMs(30); effect->giEffect = new GameInteractionEffect::RandomWind(); break; case kEffectRandomBonks: effect->category = kEffectCatRandomBonks; - effect->timeRemaining = 60000; + effect->timeRemaining = GetDurationMs(60); effect->giEffect = new GameInteractionEffect::RandomBonks(); break; @@ -497,38 +886,38 @@ CrowdControl::Effect* CrowdControl::ParseMessage(nlohmann::json dataReceived) { // Link Size Modifiers case kEffectGiantLink: effect->category = kEffectCatLinkSize; - effect->timeRemaining = 30000; + effect->timeRemaining = GetDurationMs(30); effect->giEffect = new GameInteractionEffect::ModifyLinkSize(); dynamic_cast(effect->giEffect)->parameters[0] = GI_LINK_SIZE_GIANT; break; case kEffectMinishLink: effect->category = kEffectCatLinkSize; - effect->timeRemaining = 30000; + effect->timeRemaining = GetDurationMs(30); effect->giEffect = new GameInteractionEffect::ModifyLinkSize(); dynamic_cast(effect->giEffect)->parameters[0] = GI_LINK_SIZE_MINISH; break; case kEffectPaperLink: effect->category = kEffectCatLinkSize; - effect->timeRemaining = 30000; + effect->timeRemaining = GetDurationMs(30); effect->giEffect = new GameInteractionEffect::ModifyLinkSize(); dynamic_cast(effect->giEffect)->parameters[0] = GI_LINK_SIZE_PAPER; break; case kEffectSquishedLink: effect->category = kEffectCatLinkSize; - effect->timeRemaining = 30000; + effect->timeRemaining = GetDurationMs(30); effect->giEffect = new GameInteractionEffect::ModifyLinkSize(); dynamic_cast(effect->giEffect)->parameters[0] = GI_LINK_SIZE_SQUISHED; break; case kEffectInvisibleLink: effect->category = kEffectCatLinkSize; - effect->timeRemaining = 30000; + effect->timeRemaining = GetDurationMs(30); effect->giEffect = new GameInteractionEffect::InvisibleLink(); break; // Generic Effects case kEffectRandomBombTimer: effect->category = kEffectCatRandomBombFuseTimer; - effect->timeRemaining = 60000; + effect->timeRemaining = GetDurationMs(60); effect->giEffect = new GameInteractionEffect::RandomBombFuseTimer(); break; case kEffectSetTimeToDawn: @@ -543,17 +932,17 @@ CrowdControl::Effect* CrowdControl::ParseMessage(nlohmann::json dataReceived) { // Visual Effects case kEffectNoUi: effect->category = kEffectCatUi; - effect->timeRemaining = 60000; + effect->timeRemaining = GetDurationMs(60); effect->giEffect = new GameInteractionEffect::NoUI(); break; case kEffectRainstorm: effect->category = kEffectCatWeather; - effect->timeRemaining = 30000; + effect->timeRemaining = GetDurationMs(30); effect->giEffect = new GameInteractionEffect::WeatherRainstorm(); break; case kEffectDebugMode: effect->category = kEffectCatDebugMode; - effect->timeRemaining = 30000; + effect->timeRemaining = GetDurationMs(30); effect->giEffect = new GameInteractionEffect::SetCollisionViewer(); break; case kEffectRandomCosmetics: @@ -563,22 +952,22 @@ CrowdControl::Effect* CrowdControl::ParseMessage(nlohmann::json dataReceived) { // Controls case kEffectNoZButton: effect->category = kEffectCatNoZ; - effect->timeRemaining = 30000; + effect->timeRemaining = GetDurationMs(30); effect->giEffect = new GameInteractionEffect::DisableZTargeting(); break; case kEffectReverseControls: effect->category = kEffectCatReverseControls; - effect->timeRemaining = 60000; + effect->timeRemaining = GetDurationMs(60); effect->giEffect = new GameInteractionEffect::ReverseControls(); break; case kEffectPacifistMode: effect->category = kEffectCatPacifist; - effect->timeRemaining = 15000; + effect->timeRemaining = GetDurationMs(15); effect->giEffect = new GameInteractionEffect::PacifistMode(); break; case kEffectPressRandomButtons: effect->category = kEffectCatRandomButtons; - effect->timeRemaining = 30000; + effect->timeRemaining = GetDurationMs(30); effect->giEffect = new GameInteractionEffect::PressRandomButton(); dynamic_cast(effect->giEffect)->parameters[0] = 30; break; @@ -627,3 +1016,1393 @@ CrowdControl::Effect* CrowdControl::ParseMessage(nlohmann::json dataReceived) { return effect; } + +// Local (offline) chaos CVARs +#define CVAR_CC_CHAOS_ENABLED CVAR_REMOTE_CROWD_CONTROL("ChaosEnabled") +#define CVAR_CC_CHAOS_MIN_SECONDS CVAR_REMOTE_CROWD_CONTROL("ChaosMinSeconds") +#define CVAR_CC_CHAOS_MAX_SECONDS CVAR_REMOTE_CROWD_CONTROL("ChaosMaxSeconds") +#define CVAR_CC_CHAOS_NOTIFY CVAR_REMOTE_CROWD_CONTROL("ChaosShowNotifications") +// 0 = Auto, 1 = Up, 2 = Down +#define CVAR_CC_TIMERS_GROW_DIR CVAR_REMOTE_CROWD_CONTROL("TimersGrowDir") + +// Per-effect keys: +// Remote.CrowdControl.ChaosEffectEnabled. +// Remote.CrowdControl.ChaosEffectWeight. +// Remote.CrowdControl.ChaosEffectDuration. +static std::string CCChaosEffectEnabledKey(const std::string& effectCode) { + return std::string(CVAR_REMOTE_CROWD_CONTROL("ChaosEffectEnabled.")) + effectCode; +} +static std::string CCChaosEffectWeightKey(const std::string& effectCode) { + return std::string(CVAR_REMOTE_CROWD_CONTROL("ChaosEffectWeight.")) + effectCode; +} +static std::string CCChaosEffectDurationKey(const std::string& effectCode) { + return std::string(CVAR_REMOTE_CROWD_CONTROL("ChaosEffectDuration.")) + effectCode; +} + +// Read duration in seconds, do NOT clamp (except negative -> 0). Return ms. +static long CC_GetEffectDurationMsForCode(const std::string& effectCode, int defaultSeconds) { + const std::string key = CCChaosEffectDurationKey(effectCode); + int sec = CVarGetInteger(key.c_str(), defaultSeconds); + if (sec < 0) { + sec = 0; + } + return (long)sec * 1000L; +} + +enum class ChaosUiGroup { + Spawn, + LinkModifiers, + HurtHeal, + GiveItems, + TakeItems, + LinkSize, + Generic, + Visual, + Controls, + Teleport, +}; + +static const char* ChaosGroupName(ChaosUiGroup g) { + switch (g) { + case ChaosUiGroup::Spawn: + return "Spawn Enemies and Objects"; + case ChaosUiGroup::LinkModifiers: + return "Link Modifiers"; + case ChaosUiGroup::HurtHeal: + return "Hurt or Heal Link"; + case ChaosUiGroup::GiveItems: + return "Give Items and Consumables"; + case ChaosUiGroup::TakeItems: + return "Take Items and Consumables"; + case ChaosUiGroup::LinkSize: + return "Link Size Modifiers"; + case ChaosUiGroup::Generic: + return "Generic Effects"; + case ChaosUiGroup::Visual: + return "Visual Effects"; + case ChaosUiGroup::Controls: + return "Controls"; + case ChaosUiGroup::Teleport: + return "Teleport Player"; + default: + return "Unknown"; + } +} + +struct ChaosEffectDef { + const char* uiName; + const char* code; + ChaosUiGroup group; + CCCatEnumValues category; + int defaultWeight; + bool defaultEnabled; + bool hasParameter; + int defaultParameter; + int defaultDurationSeconds; +}; + +// Keep this list in sync with ParseMessage()'s supported effect codes. +static const std::vector kChaosEffects = { + // Spawn Enemies and Objects (non-timed) + { "Cucco Storm", "spawn_cucco_storm", ChaosUiGroup::Spawn, kEffectCatSpawnActor, 50, true, false, 0, 0 }, + { "Lit Bomb", "spawn_lit_bomb", ChaosUiGroup::Spawn, kEffectCatSpawnActor, 60, true, false, 0, 0 }, + { "Explosion", "spawn_explosion", ChaosUiGroup::Spawn, kEffectCatSpawnActor, 35, true, false, 0, 0 }, + { "Arwing", "spawn_arwing", ChaosUiGroup::Spawn, kEffectCatSpawnEnemy, 20, true, false, 0, 0 }, + { "Dark Link", "spawn_darklink", ChaosUiGroup::Spawn, kEffectCatSpawnEnemy, 15, true, false, 0, 0 }, + { "Iron Knuckle", "spawn_iron_knuckle", ChaosUiGroup::Spawn, kEffectCatSpawnEnemy, 10, true, false, 0, 0 }, + { "Stalfos", "spawn_stalfos", ChaosUiGroup::Spawn, kEffectCatSpawnEnemy, 25, true, false, 0, 0 }, + { "Freezard", "spawn_freezard", ChaosUiGroup::Spawn, kEffectCatSpawnEnemy, 25, true, false, 0, 0 }, + { "Like-Like", "spawn_like_like", ChaosUiGroup::Spawn, kEffectCatSpawnEnemy, 25, true, false, 0, 0 }, + { "Gibdo", "spawn_gibdo", ChaosUiGroup::Spawn, kEffectCatSpawnEnemy, 20, true, false, 0, 0 }, + { "Keese", "spawn_keese", ChaosUiGroup::Spawn, kEffectCatSpawnEnemy, 25, true, false, 0, 0 }, + { "Ice Keese", "spawn_ice_keese", ChaosUiGroup::Spawn, kEffectCatSpawnEnemy, 35, true, false, 0, 0 }, + { "Fire Keese", "spawn_fire_keese", ChaosUiGroup::Spawn, kEffectCatSpawnEnemy, 35, true, false, 0, 0 }, + { "Wolfos", "spawn_wolfos", ChaosUiGroup::Spawn, kEffectCatSpawnEnemy, 35, true, false, 0, 0 }, + { "Wallmaster", "spawn_wallmaster", ChaosUiGroup::Spawn, kEffectCatSpawnEnemy, 15, true, false, 0, 0 }, + + // Link Modifiers (timed) + { "Take Half Damage", "take_half_damage", ChaosUiGroup::LinkModifiers, kEffectCatDamageTaken, 20, true, false, 0, + 30 }, + { "Take Double Damage", "take_double_damage", ChaosUiGroup::LinkModifiers, kEffectCatDamageTaken, 25, true, false, + 0, 30 }, + { "One Hit KO", "one_hit_ko", ChaosUiGroup::LinkModifiers, kEffectCatDamageTaken, 8, true, false, 0, 30 }, + { "Invincibility", "invincibility", ChaosUiGroup::LinkModifiers, kEffectCatDamageTaken, 15, true, false, 0, 15 }, + { "Increase Speed", "increase_speed", ChaosUiGroup::LinkModifiers, kEffectCatSpeed, 20, true, false, 0, 30 }, + { "Decrease Speed", "decrease_speed", ChaosUiGroup::LinkModifiers, kEffectCatSpeed, 20, true, false, 0, 30 }, + { "Low Gravity", "low_gravity", ChaosUiGroup::LinkModifiers, kEffectCatGravity, 20, true, false, 0, 30 }, + { "High Gravity", "high_gravity", ChaosUiGroup::LinkModifiers, kEffectCatGravity, 20, true, false, 0, 30 }, + { "Force Iron Boots", "force_iron_boots", ChaosUiGroup::LinkModifiers, kEffectCatBoots, 15, true, false, 0, 30 }, + { "Force Hover Boots", "force_hover_boots", ChaosUiGroup::LinkModifiers, kEffectCatBoots, 15, true, false, 0, 30 }, + { "Slippery Floor", "slippery_floor", ChaosUiGroup::LinkModifiers, kEffectCatSlipperyFloor, 20, true, false, 0, + 30 }, + { "Disable Ledge Grabs", "no_ledge_grabs", ChaosUiGroup::LinkModifiers, kEffectCatNoLedgeGrabs, 15, true, false, 0, + 30 }, + { "Random Wind", "random_wind", ChaosUiGroup::LinkModifiers, kEffectCatRandomWind, 15, true, false, 0, 30 }, + { "Random Bonks", "random_bonks", ChaosUiGroup::LinkModifiers, kEffectCatRandomBonks, 15, true, false, 0, 60 }, + + // Hurt or Heal Link (non-timed, some parameterized) + { "Empty Heart", "empty_heart", ChaosUiGroup::HurtHeal, kEffectCatNone, 15, true, true, 1, 0 }, + { "Fill Heart", "fill_heart", ChaosUiGroup::HurtHeal, kEffectCatNone, 15, true, true, 1, 0 }, + { "Knockback Weak", "knockback_link_weak", ChaosUiGroup::HurtHeal, kEffectCatNone, 15, true, false, 0, 0 }, + { "Knockback Strong", "knockback_link_strong", ChaosUiGroup::HurtHeal, kEffectCatNone, 10, true, false, 0, 0 }, + { "Knockback Mega", "knockback_link_mega", ChaosUiGroup::HurtHeal, kEffectCatNone, 6, true, false, 0, 0 }, + { "Burn Link", "burn_link", ChaosUiGroup::HurtHeal, kEffectCatNone, 10, true, false, 0, 0 }, + { "Freeze Link", "freeze_link", ChaosUiGroup::HurtHeal, kEffectCatNone, 10, true, false, 0, 0 }, + { "Electrocute Link", "electrocute_link", ChaosUiGroup::HurtHeal, kEffectCatNone, 10, true, false, 0, 0 }, + { "Kill Link", "kill_link", ChaosUiGroup::HurtHeal, kEffectCatNone, 2, true, false, 0, 0 }, + + // Give Items and Consumables (non-timed, some parameterized) + { "Add Heart Container", "add_heart_container", ChaosUiGroup::GiveItems, kEffectCatNone, 3, true, false, 0, 0 }, + { "Fill Magic", "fill_magic", ChaosUiGroup::GiveItems, kEffectCatNone, 6, true, false, 0, 0 }, + { "Give Rupees", "add_rupees", ChaosUiGroup::GiveItems, kEffectCatNone, 10, true, true, 50, 0 }, + { "Give Deku Shield", "give_deku_shield", ChaosUiGroup::GiveItems, kEffectCatNone, 2, true, false, 0, 0 }, + { "Give Hylian Shield", "give_hylian_shield", ChaosUiGroup::GiveItems, kEffectCatNone, 2, true, false, 0, 0 }, + { "Refill Sticks", "refill_sticks", ChaosUiGroup::GiveItems, kEffectCatNone, 6, true, true, 5, 0 }, + { "Refill Nuts", "refill_nuts", ChaosUiGroup::GiveItems, kEffectCatNone, 6, true, true, 5, 0 }, + { "Refill Bombs", "refill_bombs", ChaosUiGroup::GiveItems, kEffectCatNone, 6, true, true, 5, 0 }, + { "Refill Seeds", "refill_seeds", ChaosUiGroup::GiveItems, kEffectCatNone, 6, true, true, 10, 0 }, + { "Refill Arrows", "refill_arrows", ChaosUiGroup::GiveItems, kEffectCatNone, 6, true, true, 10, 0 }, + { "Refill Bombchus", "refill_bombchus", ChaosUiGroup::GiveItems, kEffectCatNone, 6, true, true, 5, 0 }, + + // Take Items and Consumables (non-timed, some parameterized) + { "Remove Heart Container", "remove_heart_container", ChaosUiGroup::TakeItems, kEffectCatNone, 2, true, false, 0, + 0 }, + { "Empty Magic", "empty_magic", ChaosUiGroup::TakeItems, kEffectCatNone, 6, true, false, 0, 0 }, + { "Take Rupees", "remove_rupees", ChaosUiGroup::TakeItems, kEffectCatNone, 10, true, true, 50, 0 }, + { "Take Deku Shield", "take_deku_shield", ChaosUiGroup::TakeItems, kEffectCatNone, 2, true, false, 0, 0 }, + { "Take Hylian Shield", "take_hylian_shield", ChaosUiGroup::TakeItems, kEffectCatNone, 2, true, false, 0, 0 }, + { "Take Sticks", "take_sticks", ChaosUiGroup::TakeItems, kEffectCatNone, 6, true, true, 5, 0 }, + { "Take Nuts", "take_nuts", ChaosUiGroup::TakeItems, kEffectCatNone, 6, true, true, 5, 0 }, + { "Take Bombs", "take_bombs", ChaosUiGroup::TakeItems, kEffectCatNone, 6, true, true, 5, 0 }, + { "Take Seeds", "take_seeds", ChaosUiGroup::TakeItems, kEffectCatNone, 6, true, true, 10, 0 }, + { "Take Arrows", "take_arrows", ChaosUiGroup::TakeItems, kEffectCatNone, 6, true, true, 10, 0 }, + { "Take Bombchus", "take_bombchus", ChaosUiGroup::TakeItems, kEffectCatNone, 6, true, true, 5, 0 }, + + // Link Size Modifiers (timed) + { "Giant Link", "giant_link", ChaosUiGroup::LinkSize, kEffectCatLinkSize, 10, true, false, 0, 30 }, + { "Minish Link", "minish_link", ChaosUiGroup::LinkSize, kEffectCatLinkSize, 10, true, false, 0, 30 }, + { "Paper Link", "paper_link", ChaosUiGroup::LinkSize, kEffectCatLinkSize, 10, true, false, 0, 30 }, + { "Squished Link", "squished_link", ChaosUiGroup::LinkSize, kEffectCatLinkSize, 10, true, false, 0, 30 }, + { "Invisible Link", "invisible_link", ChaosUiGroup::LinkSize, kEffectCatLinkSize, 10, true, false, 0, 30 }, + + // Generic Effects (one timed, others non-timed) + { "Random Bomb Fuse Timer", "random_bomb_timer", ChaosUiGroup::Generic, kEffectCatRandomBombFuseTimer, 10, true, + false, 0, 60 }, + { "Set Time: Dawn", "set_time_to_dawn", ChaosUiGroup::Generic, kEffectCatNone, 2, true, false, 0, 0 }, + { "Set Time: Dusk", "set_time_to_dusk", ChaosUiGroup::Generic, kEffectCatNone, 2, true, false, 0, 0 }, + + // Visual Effects (some timed) + { "No UI", "no_ui", ChaosUiGroup::Visual, kEffectCatUi, 8, true, false, 0, 60 }, + { "Rainstorm", "rainstorm", ChaosUiGroup::Visual, kEffectCatWeather, 10, true, false, 0, 30 }, + { "Debug Mode", "debug_mode", ChaosUiGroup::Visual, kEffectCatDebugMode, 2, true, false, 0, 30 }, + { "Randomize Cosmetics", "random_cosmetics", ChaosUiGroup::Visual, kEffectCatNone, 6, true, false, 0, 0 }, + + // Controls (some timed) + { "No Z Button", "no_z_button", ChaosUiGroup::Controls, kEffectCatNoZ, 10, true, false, 0, 30 }, + { "Reverse Controls", "reverse_controls", ChaosUiGroup::Controls, kEffectCatReverseControls, 10, true, false, 0, + 60 }, + { "Pacifist Mode", "pacifist_mode", ChaosUiGroup::Controls, kEffectCatPacifist, 8, true, false, 0, 15 }, + { "Press Random Buttons", "press_random_buttons", ChaosUiGroup::Controls, kEffectCatRandomButtons, 8, true, false, + 0, 30 }, + { "Clear C-Buttons", "clear_cbuttons", ChaosUiGroup::Controls, kEffectCatNone, 6, true, false, 0, 0 }, + { "Clear D-Pad", "clear_dpad", ChaosUiGroup::Controls, kEffectCatNone, 6, true, false, 0, 0 }, + + // Teleport Player (non-timed) + { "TP: Link's House", "tp_links_house", ChaosUiGroup::Teleport, kEffectCatNone, 3, true, false, 0, 0 }, + { "TP: Minuet", "tp_minuet", ChaosUiGroup::Teleport, kEffectCatNone, 2, true, false, 0, 0 }, + { "TP: Bolero", "tp_bolero", ChaosUiGroup::Teleport, kEffectCatNone, 2, true, false, 0, 0 }, + { "TP: Serenade", "tp_serenade", ChaosUiGroup::Teleport, kEffectCatNone, 2, true, false, 0, 0 }, + { "TP: Requiem", "tp_requiem", ChaosUiGroup::Teleport, kEffectCatNone, 2, true, false, 0, 0 }, + { "TP: Nocturne", "tp_nocturne", ChaosUiGroup::Teleport, kEffectCatNone, 2, true, false, 0, 0 }, + { "TP: Prelude", "tp_prelude", ChaosUiGroup::Teleport, kEffectCatNone, 2, true, false, 0, 0 }, +}; + +static std::string CC_FormatMs(long ms) { + if (ms < 0) { + ms = 0; + } + long totalSec = ms / 1000; + long m = totalSec / 60; + long s = totalSec % 60; + char buf[32]; + snprintf(buf, sizeof(buf), "%ld:%02ld", m, s); + return std::string(buf); +} + +static const char* CC_LookupUiNameForCode(const std::string& code) { + for (const auto& def : kChaosEffects) { + if (code == def.code) { + return def.uiName; + } + } + return nullptr; +} + +void CrowdControl::SyncChaosStartup() { + const bool chaosEnabledNow = (CVarGetInteger(CVAR_REMOTE_CROWD_CONTROL("ChaosEnabled"), 0) != 0); + + // If chaos is not enabled, reset guard so it can apply again next time user enables it. + if (!chaosEnabledNow) { + chaosStartupApplied = false; + return; + } + + // Start the chaos thread even if the menu is never opened. + EnsureChaosThreadStarted(); + + // Apply the "edge trigger" work once per enable. + if (!chaosStartupApplied) { + chaosStartupApplied = true; + + // If you still want Remote CC networking enabled when Chaos is on: + CVarSetInteger(CVAR_REMOTE_CROWD_CONTROL("Enabled"), 1); + Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); + + // Only call Enable once; Enable() is safe-ish but better not spam it. + if (!isEnabled) { + Enable(); + } + + // Keep hooks in sync immediately. + CrowdControl_RegisterHooks(); + } +} + +void CrowdControl::DrawEffectTimersWindowContents() { + // Keep removable effects cleanup happening while this standalone window is open + PumpPendingRemovals(); + + if (ImGui::BeginPopupContextWindow("##CCTimersContext", ImGuiPopupFlags_MouseButtonRight)) { + int mode = CVarGetInteger(CVAR_CC_TIMERS_GROW_DIR, 0); + + if (ImGui::MenuItem("Grow: Auto", nullptr, mode == 0)) { + CVarSetInteger(CVAR_CC_TIMERS_GROW_DIR, 0); + Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); + } + if (ImGui::MenuItem("Grow: Up", nullptr, mode == 1)) { + CVarSetInteger(CVAR_CC_TIMERS_GROW_DIR, 1); + Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); + } + if (ImGui::MenuItem("Grow: Down", nullptr, mode == 2)) { + CVarSetInteger(CVAR_CC_TIMERS_GROW_DIR, 2); + Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); + } + + ImGui::EndPopup(); + } + + struct Row { + std::string name; + std::string state; + std::string timeText; + long ms; + uint32_t id; + }; + + std::vector rows; + rows.reserve(16); + + { + std::scoped_lock lock(activeEffectsMutex); + for (auto* e : activeEffects) { + if (!e) { + continue; + } + + const std::string code = !e->effectCode.empty() ? e->effectCode : std::string(); + std::string name = !e->displayName.empty() ? e->displayName : code; + + // If displayName is just the code, try to upgrade to the Chaos UI name when possible. + if (!code.empty() && (name.empty() || name == code)) { + if (const char* ui = CC_LookupUiNameForCode(code)) { + name = ui; + } + } + if (name.empty()) { + name = "(Effect)"; + } + + // Timed effects + if (e->timeRemaining > 0) { + Row r; + r.name = name; + r.state = e->isPaused ? "Paused" : "Active"; + r.ms = e->timeRemaining; + r.timeText = CC_FormatMs(r.ms); + r.id = e->id; + rows.push_back(std::move(r)); + continue; + } + + // Queued one-shot effects + if (e->runOnceWhenPossible) { + Row r; + r.name = name; + r.state = "Queued"; + r.ms = 0; + r.timeText = "--:--"; + r.id = e->id; + rows.push_back(std::move(r)); + continue; + } + } + } + + if (rows.empty()) { + ImGui::TextUnformatted("No active effects."); + return; + } + + auto rankState = [](const std::string& s) { + if (s == "Active") + return 0; + if (s == "Paused") + return 1; + return 2; // Queued + }; + + std::sort(rows.begin(), rows.end(), [&](const Row& a, const Row& b) { + int ra = rankState(a.state); + int rb = rankState(b.state); + if (ra != rb) + return ra < rb; + if (a.name != b.name) + return a.name < b.name; + return a.id < b.id; + }); + + // Measure desired column widths (include headers too) + float wName = ImGui::CalcTextSize("Effect").x; + float wState = ImGui::CalcTextSize("State").x; + float wTime = ImGui::CalcTextSize("Time").x; + + for (const auto& r : rows) { + wName = std::max(wName, ImGui::CalcTextSize(r.name.c_str()).x); + wState = std::max(wState, ImGui::CalcTextSize(r.state.c_str()).x); + wTime = std::max(wTime, ImGui::CalcTextSize(r.timeText.c_str()).x); + } + + ImGuiStyle& style = ImGui::GetStyle(); + + // Add padding inside each table cell. + const float cellPadX = style.CellPadding.x * 2.0f; + + float colNameW = wName + cellPadX + 10.0f; + float colStateW = wState + cellPadX + 10.0f; + float colTimeW = wTime + cellPadX + 10.0f; + + // Total desired window width: + float desiredW = colNameW + colStateW + colTimeW + (style.WindowPadding.x * 2.0f) + 20.0f; + desiredW = std::clamp(desiredW, 260.0f, 1200.0f); + + sCCTimersDesiredWidth = desiredW; + + const ImGuiTableFlags flags = + ImGuiTableFlags_BordersInnerV | ImGuiTableFlags_RowBg | ImGuiTableFlags_SizingFixedFit; + + if (ImGui::BeginTable("##CCTimers", 3, flags)) { + ImGui::TableSetupColumn(nullptr, ImGuiTableColumnFlags_WidthFixed, colNameW); + ImGui::TableSetupColumn(nullptr, ImGuiTableColumnFlags_WidthFixed, colStateW); + ImGui::TableSetupColumn(nullptr, ImGuiTableColumnFlags_WidthFixed, colTimeW); + + for (const auto& r : rows) { + ImGui::TableNextRow(); + + ImGui::TableSetColumnIndex(0); + ImGui::TextUnformatted(r.name.c_str()); + + ImGui::TableSetColumnIndex(1); + ImGui::TextUnformatted(r.state.c_str()); + + ImGui::TableSetColumnIndex(2); + ImGui::TextUnformatted(r.timeText.c_str()); + } + + ImGui::EndTable(); + } +} + +void CrowdControlEffectTimersWindow::Draw() { + if (CVarGetInteger(CVAR_WINDOW("CrowdControlTimers"), 0) == 0) { + return; + } + if (!IsVisible() || gPlayState == nullptr) { + return; + } + + static bool sAnchorInit = false; + static float sAnchorY = 0.0f; + + static int sLastMode = -999; // 0 auto, 1 up, 2 down + static int sLastGrowDir = -999; // effective 1 up, 2 down + + static ImVec2 sPrevPos = ImVec2(FLT_MAX, FLT_MAX); + static bool sWasMoving = false; + + ImGui::PushStyleColor(ImGuiCol_WindowBg, + ImVec4(0, 0, 0, CVarGetFloat(CVAR_SETTING("Notifications.BgOpacity"), 0.5f))); + ImGui::PushStyleColor(ImGuiCol_Border, ImVec4(0, 0, 0, 0)); + ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 4.0f); + + float w = sCCTimersDesiredWidth > 0.0f ? sCCTimersDesiredWidth : 320.0f; + ImGui::SetNextWindowSizeConstraints(ImVec2(w, 0.0f), ImVec2(w, FLT_MAX)); + + ImGui::Begin("CrowdControl Timers", nullptr, + ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoNav | ImGuiWindowFlags_NoFocusOnAppearing | + ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoDocking | ImGuiWindowFlags_NoTitleBar | + ImGuiWindowFlags_NoScrollWithMouse | ImGuiWindowFlags_NoScrollbar); + + ImGuiViewport* winVp = ImGui::GetWindowViewport(); + if (winVp == nullptr) { + winVp = ImGui::GetMainViewport(); + } + ImGuiViewport* mainVp = ImGui::GetMainViewport(); + const bool inGameViewport = (winVp == mainVp); + ImGuiViewport* refVp = inGameViewport ? mainVp : winVp; + + ImVec2 prePos = ImGui::GetWindowPos(); + ImVec2 preSize = ImGui::GetWindowSize(); + + const int mode = CVarGetInteger(CVAR_CC_TIMERS_GROW_DIR, 0); // 0 auto, 1 up, 2 down + int growDir = mode; + + if (growDir == 0) { + const float workTop = refVp->WorkPos.y; + const float workBot = refVp->WorkPos.y + refVp->WorkSize.y; + + const float spaceAbove = prePos.y - workTop; + const float spaceBelow = workBot - (prePos.y + preSize.y); + + growDir = (spaceAbove > spaceBelow) ? 1 : 2; // 1=Up, 2=Down + } + + if (!sAnchorInit || mode != sLastMode || growDir != sLastGrowDir) { + sAnchorInit = true; + sLastMode = mode; + sLastGrowDir = growDir; + sAnchorY = (growDir == 1) ? (prePos.y + preSize.y) : prePos.y; + } + + DrawElement(); + + ImVec2 postPos = ImGui::GetWindowPos(); + ImVec2 postSize = ImGui::GetWindowSize(); + + const ImGuiIO& io = ImGui::GetIO(); + const bool mouseDown = io.MouseDown[ImGuiMouseButton_Left]; + + bool posChanged = false; + if (sPrevPos.x != FLT_MAX) { + posChanged = (postPos.x != sPrevPos.x) || (postPos.y != sPrevPos.y); + } + sPrevPos = postPos; + + const bool isMovingNow = mouseDown && posChanged; + const bool justStartedMoving = isMovingNow && !sWasMoving; + const bool justStoppedMoving = !mouseDown && sWasMoving; + sWasMoving = isMovingNow || (mouseDown && sWasMoving); + + if (justStartedMoving) { + sAnchorY = (growDir == 1) ? (postPos.y + postSize.y) : postPos.y; + } + + if (mouseDown && sWasMoving) { + const float grab = 32.0f; + + ImVec2 clamped = postPos; + + const float minX = refVp->WorkPos.x - postSize.x + grab; + const float maxX = refVp->WorkPos.x + refVp->WorkSize.x - grab; + clamped.x = std::clamp(clamped.x, minX, maxX); + + const float minY = refVp->WorkPos.y - postSize.y + grab; + const float maxY = refVp->WorkPos.y + refVp->WorkSize.y - grab; + clamped.y = std::clamp(clamped.y, minY, maxY); + + if (clamped.x != postPos.x || clamped.y != postPos.y) { + ImGui::SetWindowPos(clamped, ImGuiCond_Always); + postPos = clamped; + } + + sAnchorY = (growDir == 1) ? (postPos.y + postSize.y) : postPos.y; + + ImGui::End(); + ImGui::PopStyleVar(); + ImGui::PopStyleColor(2); + return; + } + + if (justStoppedMoving) { + ImVec2 droppedPos = ImGui::GetWindowPos(); + ImVec2 droppedSize = ImGui::GetWindowSize(); + sAnchorY = (growDir == 1) ? (droppedPos.y + droppedSize.y) : droppedPos.y; + postPos = droppedPos; + postSize = droppedSize; + } + + ImVec2 targetPos = postPos; + if (growDir == 1) { + targetPos.y = sAnchorY - postSize.y; + } else { + targetPos.y = sAnchorY; + } + + const float minX = refVp->WorkPos.x; + const float maxX = refVp->WorkPos.x + refVp->WorkSize.x - postSize.x; + targetPos.x = std::clamp(targetPos.x, minX, maxX); + + const float minY = refVp->WorkPos.y; + const float maxY = refVp->WorkPos.y + refVp->WorkSize.y - postSize.y; + targetPos.y = std::clamp(targetPos.y, minY, maxY); + + if (targetPos.x != postPos.x || targetPos.y != postPos.y) { + ImGui::SetWindowPos(targetPos, ImGuiCond_Always); + postPos = targetPos; + } + + sAnchorY = (growDir == 1) ? (postPos.y + postSize.y) : postPos.y; + + ImGui::End(); + + ImGui::PopStyleVar(); + ImGui::PopStyleColor(2); +} + +void CrowdControl::EnsureChaosThreadStarted() { + if (chaosThreadStarted.exchange(true)) { + return; + } + chaosThreadExit = false; + chaosThread = std::thread(&CrowdControl::ChaosLoop, this); + chaosThread.detach(); + + // Ensure main-thread hook exists as soon as chaos is used. + CrowdControl_RegisterHooks(); +} + +bool CrowdControl::CanRunChaosNow() { + // Avoid running on file select / menus. + if (gPlayState == nullptr) { + return false; + } + return true; +} + +void CrowdControl::ChaosLoop() { + std::random_device rd; + std::mt19937 rng(rd()); + chaosRunning = true; + + while (!chaosThreadExit) { + int minS = CVarGetInteger(CVAR_CC_CHAOS_MIN_SECONDS, 20); + int maxS = CVarGetInteger(CVAR_CC_CHAOS_MAX_SECONDS, 60); + if (minS < 1) { + minS = 1; + } + if (maxS < minS) { + maxS = minS; + } + + std::uniform_int_distribution delayDist(minS, maxS); + const int delay = delayDist(rng); + + int elapsed = 0; + while (elapsed < delay && !chaosThreadExit) { + std::this_thread::sleep_for(std::chrono::seconds(1)); + + bool hasQueuedWaiting = false; + { + std::scoped_lock lock(activeEffectsMutex); + for (Effect* e : activeEffects) { + if (!e) + continue; + + if (e->runOnceWhenPossible) { + hasQueuedWaiting = true; + break; + } + + if (e->timeRemaining > 0 && e->isPaused) { + hasQueuedWaiting = true; + break; + } + } + } + + (void)hasQueuedWaiting; + elapsed++; + } + + if (chaosThreadExit) { + break; + } + + if (!CVarGetInteger(CVAR_CC_CHAOS_ENABLED, 0)) { + continue; + } + if (!CanRunChaosNow()) { + continue; + } + + int totalWeight = 0; + for (const auto& def : kChaosEffects) { + const std::string enabledKey = CCChaosEffectEnabledKey(def.code); + const std::string weightKey = CCChaosEffectWeightKey(def.code); + + if (!CVarGetInteger(enabledKey.c_str(), def.defaultEnabled ? 1 : 0)) { + continue; + } + const int w = CVarGetInteger(weightKey.c_str(), def.defaultWeight); + if (w <= 0) { + continue; + } + totalWeight += w; + } + + if (totalWeight <= 0) { + continue; + } + + std::uniform_int_distribution pickDist(1, totalWeight); + int pick = pickDist(rng); + + const ChaosEffectDef* chosen = nullptr; + for (const auto& def : kChaosEffects) { + const std::string enabledKey = CCChaosEffectEnabledKey(def.code); + const std::string weightKey = CCChaosEffectWeightKey(def.code); + + if (!CVarGetInteger(enabledKey.c_str(), def.defaultEnabled ? 1 : 0)) { + continue; + } + const int w = CVarGetInteger(weightKey.c_str(), def.defaultWeight); + if (w <= 0) { + continue; + } + + pick -= w; + if (pick <= 0) { + chosen = &def; + break; + } + } + + if (chosen != nullptr) { + TriggerLocalEffectByCode(chosen->code, true); + + if (CVarGetInteger(CVAR_CC_CHAOS_NOTIFY, 1)) { + Notification::Emit({ + .message = std::string("Chaos: ") + chosen->uiName, + }); + } + } + } + + chaosRunning = false; +} + +void CrowdControl::TriggerLocalEffectByCode(const char* effectCode, bool allowTimed) { + static std::atomic sLocalId{ 100000 }; + nlohmann::json payload; + payload["id"] = sLocalId.fetch_add(1); + payload["type"] = 0; + payload["viewer"] = "Chaos"; + payload["code"] = effectCode; + + payload["parameters"] = nlohmann::json::array(); + for (const auto& def : kChaosEffects) { + if (strcmp(def.code, effectCode) == 0) { + if (def.hasParameter) { + payload["parameters"].push_back(def.defaultParameter); + } + break; + } + } + + Effect* effect = ParseMessage(payload); + if (!effect) { + return; + } + + effect->isChaos = true; + + for (const auto& def : kChaosEffects) { + if (strcmp(def.code, effectCode) == 0) { + effect->displayName = def.uiName; + break; + } + } + + if (!effect->timeRemaining || !allowTimed) { + EffectResult r = CrowdControl::ExecuteEffect(effect); + if (r == EffectResult::Success) { + delete effect; + return; + } + if (r == EffectResult::Retry) { + effect->runOnceWhenPossible = true; + effect->timeRemaining = 0; + effect->isPaused = false; + effect->lastExecutionResult = EffectResult::Retry; + + std::scoped_lock lock(activeEffectsMutex); + activeEffects.push_back(effect); + return; + } + delete effect; + return; + } + + { + std::scoped_lock lock(activeEffectsMutex); + for (Effect* active : activeEffects) { + if (active && active->timeRemaining > 0 && active->category == effect->category && + active->id < effect->id) { + effect->isPaused = true; + break; + } + } + } + + if (effect->isPaused) { + std::scoped_lock lock(activeEffectsMutex); + activeEffects.push_back(effect); + return; + } + + EffectResult can = CrowdControl::CanApplyEffect(effect); + if (can == EffectResult::Failure) { + delete effect; + return; + } + + if (can == EffectResult::Retry) { + effect->isPaused = true; + std::scoped_lock lock(activeEffectsMutex); + activeEffects.push_back(effect); + return; + } + + { + std::scoped_lock lock(activeEffectsMutex); + activeEffects.push_back(effect); + } +} + +void CrowdControl::ClearTimedEffects() { + std::vector> toRemove; + + { + std::scoped_lock lock(activeEffectsMutex); + + auto it = activeEffects.begin(); + while (it != activeEffects.end()) { + Effect* e = *it; + if (!e) { + it = activeEffects.erase(it); + continue; + } + + if (e->timeRemaining > 0) { + if (e->giEffect) { + if (auto* removable = dynamic_cast(e->giEffect)) { + toRemove.push_back({ removable, e->category }); + } + } + + it = activeEffects.erase(it); + delete e; + continue; + } + + ++it; + } + } + + if (!toRemove.empty()) { + std::scoped_lock rlock(pendingRemovalsMutex); + for (auto& [r, cat] : toRemove) { + pendingRemovals.push_back({ r, cat }); + } + } + + PumpPendingRemovals(); +} + +void CrowdControl::DrawChaosWindowContents() { + if (CVarGetInteger(CVAR_CC_CHAOS_ENABLED, 0) != 0) { + EnsureChaosThreadStarted(); + } + + CrowdControl_RegisterHooks(); + PumpPendingRemovals(); + + DrawChaosUi(); +} + +struct ChaosThemeScope { + int colorCount = 0; + + ChaosThemeScope() { + UIWidgets::PushStyleInput(THEME_COLOR); + UIWidgets::PushStyleCheckbox(THEME_COLOR); + UIWidgets::PushStyleButton(THEME_COLOR); + + const ImVec4 accent = UIWidgets::ColorValues.at(THEME_COLOR); + + // No transparency variants (alpha stays 1.0) + ImVec4 accentSoft = accent; + ImVec4 accentHover = accent; + ImVec4 accentActive = accent; + + accentSoft.w = 1.0f; + accentHover.w = 1.0f; + accentActive.w = 1.0f; + + // Make the slider "track" darker than the grab (no alpha, just darker RGB) + ImVec4 track = accent; + track.x *= 1.0f; + track.y *= 1.0f; + track.z *= 1.0f; + track.w = 1.0f; + + // Make the slider handle brighter than everything else + ImVec4 grab = accent; + grab.x = std::min(grab.x * 1.25f, 1.0f); + grab.y = std::min(grab.y * 1.25f, 1.0f); + grab.z = std::min(grab.z * 1.25f, 1.0f); + grab.w = 1.0f; + + ImVec4 grabActive = accent; + grabActive.x = std::min(grabActive.x * 1.45f, 1.0f); + grabActive.y = std::min(grabActive.y * 1.45f, 1.0f); + grabActive.z = std::min(grabActive.z * 1.45f, 1.0f); + grabActive.w = 1.0f; + + auto push = [&](ImGuiCol idx, ImVec4 c) { + ImGui::PushStyleColor(idx, c); + colorCount++; + }; + + // White grab + checkmark, semi-transparent + ImVec4 white075 = ImVec4(1.0f, 1.0f, 1.0f, 0.75f); + ImVec4 white050 = ImVec4(1.0f, 1.0f, 1.0f, 0.50f); + + // Slider handle + push(ImGuiCol_SliderGrab, white050); + push(ImGuiCol_SliderGrabActive, white050); + + // Checkbox checkmark (not the box) + push(ImGuiCol_CheckMark, white075); + + // Buttons (this fixes SmallButton +/- being grey) + push(ImGuiCol_Button, accentSoft); + push(ImGuiCol_ButtonHovered, accentHover); + push(ImGuiCol_ButtonActive, accentActive); + + // CollapsingHeader / Table header colors (this fixes category boxes) + push(ImGuiCol_Header, accentSoft); + push(ImGuiCol_HeaderHovered, accentHover); + push(ImGuiCol_HeaderActive, accentActive); + + // Slider track + grab (this fixes "bar is same color as slider box") + push(ImGuiCol_FrameBg, track); + push(ImGuiCol_FrameBgHovered, track); + push(ImGuiCol_FrameBgActive, track); + + // Tabs + push(ImGuiCol_Tab, accentSoft); + push(ImGuiCol_TabHovered, accentHover); + push(ImGuiCol_TabActive, accentActive); + push(ImGuiCol_TabUnfocused, accentSoft); + push(ImGuiCol_TabUnfocusedActive, accentHover); + + // Column resize bars / separators + push(ImGuiCol_Separator, accentSoft); + push(ImGuiCol_SeparatorHovered, accentHover); + push(ImGuiCol_SeparatorActive, accentActive); + } + + ~ChaosThemeScope() { + if (colorCount > 0) { + ImGui::PopStyleColor(colorCount); + } + + UIWidgets::PopStyleButton(); + UIWidgets::PopStyleCheckbox(); + UIWidgets::PopStyleInput(); + } +}; + +static bool CC_DrawMinusSliderPlusInt(const char* id, int* value, int minV, int maxV, const char* format = nullptr) { + bool changed = false; + + const float btnW = ImGui::GetFrameHeight(); + const float spacing = ImGui::GetStyle().ItemSpacing.x; + + if (UIWidgets::Button("-", UIWidgets::ButtonOptions().Color(THEME_COLOR).Size(ImVec2(btnW, 0)))) { + int nv = std::max(minV, *value - 1); + changed |= (nv != *value); + *value = nv; + } + + ImGui::SameLine(0.0f, spacing); + + float avail = ImGui::GetContentRegionAvail().x; + float sliderW = avail - btnW - spacing; + sliderW = std::max(sliderW, 80.0f); + + ImGui::SetNextItemWidth(sliderW); + + ImGui::PushID(id); + if (format) { + changed |= ImGui::SliderInt("##slider", value, minV, maxV, format); + } else { + changed |= ImGui::SliderInt("##slider", value, minV, maxV); + } + ImGui::PopID(); + + ImGui::SameLine(0.0f, spacing); + + if (UIWidgets::Button("+", UIWidgets::ButtonOptions().Color(THEME_COLOR).Size(ImVec2(btnW, 0)))) { + int nv = std::min(maxV, *value + 1); + changed |= (nv != *value); + *value = nv; + } + + return changed; +} + +void CrowdControl::DrawChaosUi() { + ChaosThemeScope theme; // Top controls + bool enabled = CVarGetInteger(CVAR_CC_CHAOS_ENABLED, 0) != 0; + if (ImGui::Checkbox("Enable Chaos Mode", &enabled)) { + CVarSetInteger(CVAR_CC_CHAOS_ENABLED, enabled ? 1 : 0); + Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); + + CrowdControl_RegisterHooks(); + } + + static bool sWasChaosEnabled = false; + bool chaosEnabledNow = CVarGetInteger(CVAR_CC_CHAOS_ENABLED, 0) != 0; + + if (chaosEnabledNow && !sWasChaosEnabled) { + CVarSetInteger(CVAR_REMOTE_CROWD_CONTROL("Enabled"), 1); + Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); + CrowdControl::Instance->Enable(); + } + sWasChaosEnabled = chaosEnabledNow; + + bool showNotif = CVarGetInteger(CVAR_CC_CHAOS_NOTIFY, 1) != 0; + if (ImGui::Checkbox("Show notifications", &showNotif)) { + CVarSetInteger(CVAR_CC_CHAOS_NOTIFY, showNotif ? 1 : 0); + Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); + } + + // Timers window toggle + bool showTimers = CVarGetInteger(CVAR_WINDOW("CrowdControlTimers"), 0) != 0; + ImGui::SameLine(); + if (ImGui::Checkbox("Show timers window", &showTimers)) { + CVarSetInteger(CVAR_WINDOW("CrowdControlTimers"), showTimers ? 1 : 0); + Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); + } + + int minS = CVarGetInteger(CVAR_CC_CHAOS_MIN_SECONDS, 20); + int maxS = CVarGetInteger(CVAR_CC_CHAOS_MAX_SECONDS, 60); + if (minS < 1) + minS = 1; + if (maxS < minS) + maxS = minS; + + // Min delay + ImGui::AlignTextToFramePadding(); + ImGui::TextUnformatted("Min Delay (s)"); + ImGui::SameLine(); + + ImGui::PushID("ChaosMinDelayRow"); + if (CC_DrawMinusSliderPlusInt("minDelay", &minS, 1, 600)) { + if (maxS < minS) { + maxS = minS; + } + } + ImGui::PopID(); + + // Max delay + ImGui::AlignTextToFramePadding(); + ImGui::TextUnformatted("Max Delay (s)"); + ImGui::SameLine(); + + ImGui::PushID("ChaosMaxDelayRow"); + if (CC_DrawMinusSliderPlusInt("maxDelay", &maxS, 1, 600)) { + if (maxS < minS) { + maxS = minS; + } + } + ImGui::PopID(); + + if (CVarGetInteger(CVAR_CC_CHAOS_MIN_SECONDS, 20) != minS) { + CVarSetInteger(CVAR_CC_CHAOS_MIN_SECONDS, minS); + Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); + } + if (CVarGetInteger(CVAR_CC_CHAOS_MAX_SECONDS, 60) != maxS) { + CVarSetInteger(CVAR_CC_CHAOS_MAX_SECONDS, maxS); + Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); + } + + ImGui::Spacing(); + + // Reset Defaults: turns everything ON + resets weights + resets durations + if (ImGui::Button("Reset Defaults")) { + for (const auto& def : kChaosEffects) { + const std::string enabledKey = CCChaosEffectEnabledKey(def.code); + const std::string weightKey = CCChaosEffectWeightKey(def.code); + const std::string durationKey = CCChaosEffectDurationKey(def.code); + + CVarSetInteger(enabledKey.c_str(), 1); + CVarSetInteger(weightKey.c_str(), def.defaultWeight); + + if (def.defaultDurationSeconds > 0) { + CVarSetInteger(durationKey.c_str(), def.defaultDurationSeconds); + } + } + Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); + } + ImGui::SameLine(); + if (ImGui::Button("Clear Active Effects")) { + if (CrowdControl::Instance) { + CrowdControl::Instance->ClearTimedEffects(); + } + } + + ImGui::Separator(); + + if (ImGui::BeginTabBar("ChaosTabs")) { + + if (ImGui::BeginTabItem("Effects")) { + // Enable/Disable all (exact behavior: flip ChaosEffectEnabled.* for every effect) + if (ImGui::Button("Enable All")) { + for (const auto& def : kChaosEffects) { + CVarSetInteger(CCChaosEffectEnabledKey(def.code).c_str(), 1); + } + Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); + } + ImGui::SameLine(); + if (ImGui::Button("Disable All")) { + for (const auto& def : kChaosEffects) { + CVarSetInteger(CCChaosEffectEnabledKey(def.code).c_str(), 0); + } + Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); + } + + ImGui::Separator(); + + // Expand/Collapse all (Effects) + static int sSetAllGroupsOpenEffects = 0; + if (ImGui::Button("Expand All##Effects")) { + sSetAllGroupsOpenEffects = 1; + } + ImGui::SameLine(); + if (ImGui::Button("Collapse All##Effects")) { + sSetAllGroupsOpenEffects = 2; + } + ImGui::SameLine(); + if (ImGui::Button("Reset All Weights")) { + for (const auto& def : kChaosEffects) { + CVarSetInteger(CCChaosEffectWeightKey(def.code).c_str(), def.defaultWeight); + } + Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); + } + + ImGui::Separator(); + + auto drawGroupEffects = [&](ChaosUiGroup group) { + if (sSetAllGroupsOpenEffects == 1) { + ImGui::SetNextItemOpen(true, ImGuiCond_Always); + } else if (sSetAllGroupsOpenEffects == 2) { + ImGui::SetNextItemOpen(false, ImGuiCond_Always); + } + + if (!ImGui::CollapsingHeader(ChaosGroupName(group), ImGuiTreeNodeFlags_DefaultOpen)) { + return; + } + + if (ImGui::BeginTable("##ChaosTable", 4, ImGuiTableFlags_BordersInnerV | ImGuiTableFlags_RowBg)) { + ImGui::TableSetupColumn("On", ImGuiTableColumnFlags_WidthFixed, 28.0f); + ImGui::TableSetupColumn("Effect", ImGuiTableColumnFlags_WidthStretch, 2.0f); + ImGui::TableSetupColumn("Weight", ImGuiTableColumnFlags_WidthStretch, 3.0f); + ImGui::TableSetupColumn("Reset", ImGuiTableColumnFlags_WidthFixed, 60.0f); + + // Custom clickable header row + ImGui::TableNextRow(ImGuiTableRowFlags_Headers); + + // "On" header (clickable) + ImGui::TableSetColumnIndex(0); + ImGui::TableHeader("On"); + + ImRect onRect(ImGui::GetItemRectMin(), ImGui::GetItemRectMax()); + ImGui::SetCursorScreenPos(onRect.Min); + ImGui::InvisibleButton("##hdr_on", onRect.GetSize()); + + bool onHovered = ImGui::IsItemHovered(); + bool onClicked = ImGui::IsItemClicked(); + + if (onHovered) { + ImGui::GetWindowDrawList()->AddRectFilled(onRect.Min, onRect.Max, + ImGui::GetColorU32(ImGuiCol_HeaderHovered)); + } + + if (onClicked) { + bool anyDisabled = false; + for (const auto& def : kChaosEffects) { + if (def.group != group) + continue; + + const std::string key = CCChaosEffectEnabledKey(def.code); + if (!CVarGetInteger(key.c_str(), def.defaultEnabled ? 1 : 0)) { + anyDisabled = true; + break; + } + } + + const int newValue = anyDisabled ? 1 : 0; + for (const auto& def : kChaosEffects) { + if (def.group != group) + continue; + + CVarSetInteger(CCChaosEffectEnabledKey(def.code).c_str(), newValue); + } + + Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); + } + + // "Effect" header + ImGui::TableSetColumnIndex(1); + ImGui::TableHeader("Effect"); + + // "Weight" header + ImGui::TableSetColumnIndex(2); + ImGui::TableHeader("Weight"); + + // "Reset" header (clickable, weight-only) + ImGui::TableSetColumnIndex(3); + ImGui::TableHeader("Reset"); + + ImRect resetRect(ImGui::GetItemRectMin(), ImGui::GetItemRectMax()); + ImGui::SetCursorScreenPos(resetRect.Min); + ImGui::InvisibleButton("##hdr_reset", resetRect.GetSize()); + + bool resetHovered = ImGui::IsItemHovered(); + bool resetClicked = ImGui::IsItemClicked(); + + if (resetHovered) { + ImGui::GetWindowDrawList()->AddRectFilled(resetRect.Min, resetRect.Max, + ImGui::GetColorU32(ImGuiCol_HeaderHovered)); + } + + if (resetClicked) { + for (const auto& def : kChaosEffects) { + if (def.group != group) + continue; + + CVarSetInteger(CCChaosEffectWeightKey(def.code).c_str(), def.defaultWeight); + } + + Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); + } + + // Rows + for (const auto& def : kChaosEffects) { + if (def.group != group) { + continue; + } + + const std::string enabledKey = CCChaosEffectEnabledKey(def.code); + const std::string weightKey = CCChaosEffectWeightKey(def.code); + bool effEnabled = CVarGetInteger(enabledKey.c_str(), def.defaultEnabled ? 1 : 0) != 0; + int weight = CVarGetInteger(weightKey.c_str(), def.defaultWeight); + + ImGui::TableNextRow(); + + // On + ImGui::TableSetColumnIndex(0); + std::string onId = std::string("##on_") + def.code; + if (ImGui::Checkbox(onId.c_str(), &effEnabled)) { + CVarSetInteger(enabledKey.c_str(), effEnabled ? 1 : 0); + Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); + } + + // Name + ImGui::TableSetColumnIndex(1); + ImGui::TextUnformatted(def.uiName); + + // Weight + ImGui::TableSetColumnIndex(2); + + ImGui::PushID(def.code); + + CC_DrawMinusSliderPlusInt("weight", &weight, 0, 100); + + ImGui::PopID(); + + if (CVarGetInteger(weightKey.c_str(), def.defaultWeight) != weight) { + CVarSetInteger(weightKey.c_str(), weight); + Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); + } + + // Reset (row): weight only + ImGui::TableSetColumnIndex(3); + std::string rId = std::string("Reset##") + def.code; + if (ImGui::Button(rId.c_str())) { + CVarSetInteger(weightKey.c_str(), def.defaultWeight); + Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); + } + } + + ImGui::EndTable(); + } + }; + + drawGroupEffects(ChaosUiGroup::Spawn); + drawGroupEffects(ChaosUiGroup::LinkModifiers); + drawGroupEffects(ChaosUiGroup::HurtHeal); + drawGroupEffects(ChaosUiGroup::GiveItems); + drawGroupEffects(ChaosUiGroup::TakeItems); + drawGroupEffects(ChaosUiGroup::LinkSize); + drawGroupEffects(ChaosUiGroup::Generic); + drawGroupEffects(ChaosUiGroup::Visual); + drawGroupEffects(ChaosUiGroup::Controls); + drawGroupEffects(ChaosUiGroup::Teleport); + + sSetAllGroupsOpenEffects = 0; + + ImGui::EndTabItem(); + } + + if (ImGui::BeginTabItem("Durations")) { + static int sSetAllGroupsOpenDur = 0; + + if (ImGui::Button("Expand All##Dur")) { + sSetAllGroupsOpenDur = 1; + } + ImGui::SameLine(); + if (ImGui::Button("Collapse All##Dur")) { + sSetAllGroupsOpenDur = 2; + } + ImGui::SameLine(); + if (ImGui::Button("Reset All Durations")) { + for (const auto& def : kChaosEffects) { + if (def.defaultDurationSeconds <= 0) { + continue; + } + CVarSetInteger(CCChaosEffectDurationKey(def.code).c_str(), def.defaultDurationSeconds); + } + Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); + } + + ImGui::Separator(); + + auto drawGroupDur = [&](ChaosUiGroup group) { + if (sSetAllGroupsOpenDur == 1) { + ImGui::SetNextItemOpen(true, ImGuiCond_Always); + } else if (sSetAllGroupsOpenDur == 2) { + ImGui::SetNextItemOpen(false, ImGuiCond_Always); + } + + if (!ImGui::CollapsingHeader(ChaosGroupName(group), ImGuiTreeNodeFlags_DefaultOpen)) { + return; + } + + const ImGuiTableFlags durFlags = + ImGuiTableFlags_BordersInnerV | ImGuiTableFlags_RowBg | ImGuiTableFlags_SizingFixedFit; + + if (ImGui::BeginTable("##ChaosDurTable", 3, durFlags)) { + ImGui::TableSetupColumn("Effect", ImGuiTableColumnFlags_WidthStretch, 2.0f); + ImGui::TableSetupColumn("Duration", ImGuiTableColumnFlags_WidthStretch, 3.0f); + ImGui::TableSetupColumn("Reset", ImGuiTableColumnFlags_WidthFixed, 64.0f); + + for (const auto& def : kChaosEffects) { + if (def.group != group) { + continue; + } + if (def.defaultDurationSeconds <= 0) { + continue; // only timed effects + } + + const std::string durationKey = CCChaosEffectDurationKey(def.code); + int sec = CVarGetInteger(durationKey.c_str(), def.defaultDurationSeconds); + + ImGui::TableNextRow(); + + // Effect name + ImGui::TableSetColumnIndex(0); + ImGui::TextUnformatted(def.uiName); + + // Duration controls: [-] [slider] [+] + ImGui::TableSetColumnIndex(1); + ImGui::PushID(def.code); + + int durSec = CVarGetInteger(durationKey.c_str(), def.defaultDurationSeconds); + if (durSec < 0) { + durSec = 0; + } + + // UI range clamp (slider is 0..120) + int uiSec = std::clamp(durSec, 0, 120); + + if (CC_DrawMinusSliderPlusInt("dur", &uiSec, 0, 120, "%ds")) { + durSec = uiSec; + } + + ImGui::PopID(); + + if (CVarGetInteger(durationKey.c_str(), def.defaultDurationSeconds) != durSec) { + CVarSetInteger(durationKey.c_str(), durSec); + Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); + } + + // Reset button + ImGui::TableSetColumnIndex(2); + std::string defId = std::string("Reset##") + def.code; + if (ImGui::Button(defId.c_str())) { + CVarSetInteger(durationKey.c_str(), def.defaultDurationSeconds); + Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); + } + } + + ImGui::EndTable(); + } + }; + + auto groupHasTimed = [&](ChaosUiGroup g) -> bool { + for (const auto& def : kChaosEffects) { + if (def.group == g && def.defaultDurationSeconds > 0) { + return true; + } + } + return false; + }; + + for (ChaosUiGroup g : { + ChaosUiGroup::Spawn, + ChaosUiGroup::LinkModifiers, + ChaosUiGroup::HurtHeal, + ChaosUiGroup::GiveItems, + ChaosUiGroup::TakeItems, + ChaosUiGroup::LinkSize, + ChaosUiGroup::Generic, + ChaosUiGroup::Visual, + ChaosUiGroup::Controls, + ChaosUiGroup::Teleport, + }) { + if (groupHasTimed(g)) { + drawGroupDur(g); + } + } + + sSetAllGroupsOpenDur = 0; + + ImGui::EndTabItem(); + } + + ImGui::EndTabBar(); + } +} diff --git a/soh/soh/Network/CrowdControl/CrowdControl.h b/soh/soh/Network/CrowdControl/CrowdControl.h index 5526a254c2c..6cc4c8d0d9a 100644 --- a/soh/soh/Network/CrowdControl/CrowdControl.h +++ b/soh/soh/Network/CrowdControl/CrowdControl.h @@ -5,9 +5,15 @@ #include #include #include +#include +#include +#include +#include +#include #include "soh/Network/Network.h" #include "soh/Enhancements/game-interactor/GameInteractor.h" +#include class CrowdControl : public Network { private: @@ -50,26 +56,43 @@ class CrowdControl : public Network { }; typedef struct Effect { - uint32_t id; - uint32_t spawnParams[2]; + uint32_t id = 0; + uint32_t spawnParams[2] = { 0, 0 }; uint32_t category = 0; - long timeRemaining; - GameInteractionEffectBase* giEffect; + long timeRemaining = 0; + GameInteractionEffectBase* giEffect = nullptr; std::string viewerName; + std::string effectCode; + std::string displayName; + // Metadata used while executing (only for timed effects) - bool isPaused; - EffectResult lastExecutionResult; + bool isPaused = false; + bool runOnceWhenPossible = false; // for queued non-timed effects that must only fire once + bool isChaos = false; // owned by offline Chaos loop; remote CC thread must ignore + EffectResult lastExecutionResult = EffectResult::Initiate; } Effect; std::thread ccThreadProcess; + std::atomic chaosThreadStarted{ false }; + std::atomic chaosThreadExit{ false }; + std::atomic chaosRunning{ false }; + std::thread chaosThread; + std::mutex pendingRemovalsMutex; + std::vector> pendingRemovals; std::vector activeEffects; std::mutex activeEffectsMutex; + void PumpPendingRemovals(); + void EnsureChaosThreadStarted(); + void ChaosLoop(); + bool CanRunChaosNow(); + void TriggerLocalEffectByCode(const char* effectCode, bool allowTimed); + void DrawChaosUi(); void HandleRemoteData(nlohmann::json payload); void ProcessActiveEffects(); - + void ProcessActiveEffectsOnce(); void EmitMessage(uint32_t eventId, long timeRemaining, EffectResult status); Effect* ParseMessage(nlohmann::json payload); EffectResult ExecuteEffect(Effect* effect); @@ -78,10 +101,50 @@ class CrowdControl : public Network { public: static CrowdControl* Instance; + bool HasPendingWork(); void Enable(); void OnIncomingJson(nlohmann::json payload); void OnConnected(); void OnDisconnected(); + void SyncChaosStartup(); + bool chaosStartupApplied = false; + void DrawChaosWindowContents(); + void ClearTimedEffects(); + void DrawEffectTimersWindowContents(); + void MainThreadTick(); +}; + +class CrowdControlChaosWindow : public Ship::GuiWindow { + public: + using GuiWindow::GuiWindow; + + void InitElement() override { + } + void UpdateElement() override { + } + void DrawElement() override { + if (CrowdControl::Instance) { + CrowdControl::Instance->DrawChaosWindowContents(); + } + } +}; + +class CrowdControlEffectTimersWindow : public Ship::GuiWindow { + public: + using GuiWindow::GuiWindow; + + void InitElement() override { + } + void UpdateElement() override { + } + + void DrawElement() override { + if (CrowdControl::Instance) { + CrowdControl::Instance->DrawEffectTimersWindowContents(); + } + } + + void Draw() override; }; #endif // __cplusplus diff --git a/soh/soh/Network/Network.cpp b/soh/soh/Network/Network.cpp index d9edb5550f7..6eae1f3f14e 100644 --- a/soh/soh/Network/Network.cpp +++ b/soh/soh/Network/Network.cpp @@ -46,6 +46,9 @@ void Network::OnConnected() { void Network::OnDisconnected() { } +void Network::ProcessOutgoingPackets() { +} + void Network::SendDataToRemote(const char* payload) { #ifdef ENABLE_REMOTE_CONTROL SPDLOG_DEBUG("[Network] Sending data: {}", payload); @@ -68,6 +71,7 @@ void Network::ReceiveFromServer() { if (networkSocket) { isConnected = true; + receivedData.clear(); SPDLOG_INFO("[Network] Connection to server established!"); OnConnected(); @@ -90,7 +94,11 @@ void Network::ReceiveFromServer() { break; } + // Always process outgoing packets + ProcessOutgoingPackets(); + if (socketsReady == 0) { + // No incoming data continue; } @@ -119,9 +127,15 @@ void Network::ReceiveFromServer() { } } + if (socketSet) { + SDLNet_FreeSocketSet(socketSet); + } + if (isConnected) { SDLNet_TCP_Close(networkSocket); + networkSocket = nullptr; isConnected = false; + receivedData.clear(); OnDisconnected(); SPDLOG_INFO("[Network] Ending receiving thread..."); } diff --git a/soh/soh/Network/Network.h b/soh/soh/Network/Network.h index a04ece71e0a..f1ec42c024f 100644 --- a/soh/soh/Network/Network.h +++ b/soh/soh/Network/Network.h @@ -44,6 +44,7 @@ class Network { virtual void OnIncomingJson(nlohmann::json payload); virtual void OnConnected(); virtual void OnDisconnected(); + virtual void ProcessOutgoingPackets(); void SendDataToRemote(const char* payload); virtual void SendJsonToRemote(nlohmann::json packet); }; diff --git a/soh/soh/Notification/Notification.cpp b/soh/soh/Notification/Notification.cpp index c7084196096..7c01e9a1134 100644 --- a/soh/soh/Notification/Notification.cpp +++ b/soh/soh/Notification/Notification.cpp @@ -15,99 +15,166 @@ static uint32_t nextId = 0; static std::vector notifications = {}; void Window::Draw() { - auto vp = ImGui::GetMainViewport(); + ImGuiViewport* vp = ImGui::GetMainViewport(); + if (!vp) { + return; + } const float margin = 30.0f; const float padding = 10.0f; + const float boxPaddingX = 12.0f; + const float boxPaddingY = 10.0f; + const float rounding = 6.0f; + const float iconSize = 32.0f; + const float iconTextSpacing = 8.0f; int position = CVarGetInteger(CVAR_SETTING("Notifications.Position"), 3); + if (position == 4) { // Hidden + return; + } - // Top Left + // Base anchor position (same as your code) ImVec2 basePosition; switch (position) { - case 0: // Top Left + case 0: basePosition = ImVec2(vp->Pos.x + margin, vp->Pos.y + margin); - break; - case 1: // Top Right + break; // TL + case 1: basePosition = ImVec2(vp->Pos.x + vp->Size.x - margin, vp->Pos.y + margin); - break; - case 2: // Bottom Left + break; // TR + case 2: basePosition = ImVec2(vp->Pos.x + margin, vp->Pos.y + vp->Size.y - margin); - break; - case 3: // Bottom Right + break; // BL + case 3: basePosition = ImVec2(vp->Pos.x + vp->Size.x - margin, vp->Pos.y + vp->Size.y - margin); - break; - case 4: // Hidden + break; // BR + default: return; } - ImGui::PushStyleColor(ImGuiCol_WindowBg, - ImVec4(0, 0, 0, CVarGetFloat(CVAR_SETTING("Notifications.BgOpacity"), 0.5f))); - ImGui::PushStyleColor(ImGuiCol_Border, ImVec4(0, 0, 0, 0)); - ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 4.0f); + ImDrawList* dl = ImGui::GetBackgroundDrawList(vp); - for (int index = 0; index < notifications.size(); ++index) { - auto& notification = notifications[index]; - int inverseIndex = -ABS(index - (notifications.size() - 1)); + // You were using SetWindowFontScale; for draw lists, use font size explicitly. + const float scale = CVarGetFloat(CVAR_SETTING("Notifications.Size"), 1.8f); + ImFont* font = ImGui::GetFont(); + const float fontSize = ImGui::GetFontSize() * scale; - ImGui::SetNextWindowViewport(vp->ID); - if (notification.remainingTime < 4.0f) { - ImGui::PushStyleVar(ImGuiStyleVar_Alpha, (notification.remainingTime - 1) / 3.0f); - } else { - ImGui::PushStyleVar(ImGuiStyleVar_Alpha, 1.0f); + // Stack newest at the edge (similar to your inverseIndex logic) + float stackOffsetY = 0.0f; + + // Draw from newest -> oldest so stacking feels natural + for (int i = (int)notifications.size() - 1; i >= 0; --i) { + const Options& n = notifications[i]; + + float alpha = 1.0f; + if (n.remainingTime < 4.0f) { + alpha = (n.remainingTime - 1.0f) / 3.0f; + if (alpha < 0.0f) + alpha = 0.0f; + if (alpha > 1.0f) + alpha = 1.0f; + } + + // Text sizes (prefix/message/suffix) + const bool hasIcon = (n.itemIcon != nullptr); + const bool hasPrefix = !n.prefix.empty(); + const bool hasSuffix = !n.suffix.empty(); + + // Measure text using the scaled font size + ImVec2 prefixSz(0, 0), msgSz(0, 0), suffixSz(0, 0); + + if (hasPrefix) + prefixSz = font->CalcTextSizeA(fontSize, FLT_MAX, 0.0f, n.prefix.c_str()); + msgSz = font->CalcTextSizeA(fontSize, FLT_MAX, 0.0f, n.message.c_str()); + if (hasSuffix) + suffixSz = font->CalcTextSizeA(fontSize, FLT_MAX, 0.0f, n.suffix.c_str()); + + float contentW = 0.0f; + float contentH = 0.0f; + + if (hasIcon) { + contentW += iconSize + iconTextSpacing; + contentH = (iconSize > contentH) ? iconSize : contentH; } - ImGui::Begin(("notification#" + std::to_string(notification.id)).c_str(), nullptr, - ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoNav | ImGuiWindowFlags_NoFocusOnAppearing | - ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoDocking | ImGuiWindowFlags_NoTitleBar | - ImGuiWindowFlags_NoScrollWithMouse | ImGuiWindowFlags_NoInputs | ImGuiWindowFlags_NoMove | - ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoSavedSettings); - - ImGui::SetWindowFontScale(CVarGetFloat(CVAR_SETTING("Notifications.Size"), 1.8f)); // Make this adjustable - - ImVec2 notificationPos; - switch (position) { - case 0: // Top Left - notificationPos = - ImVec2(basePosition.x, basePosition.y + ((ImGui::GetWindowSize().y + padding) * inverseIndex)); - break; - case 1: // Top Right - notificationPos = ImVec2(basePosition.x - ImGui::GetWindowSize().x, - basePosition.y + ((ImGui::GetWindowSize().y + padding) * inverseIndex)); - break; - case 2: // Bottom Left - notificationPos = ImVec2(basePosition.x, - basePosition.y - ((ImGui::GetWindowSize().y + padding) * (inverseIndex + 1))); - break; - case 3: // Bottom Right - notificationPos = ImVec2(basePosition.x - ImGui::GetWindowSize().x, - basePosition.y - ((ImGui::GetWindowSize().y + padding) * (inverseIndex + 1))); - break; + const float segSpacing = ImGui::GetStyle().ItemSpacing.x; + + float spacingW = 0.0f; + + if (hasPrefix && !n.message.empty()) + spacingW += segSpacing; + + if (hasSuffix) + spacingW += segSpacing; + + spacingW += segSpacing; + + contentW += prefixSz.x + msgSz.x + suffixSz.x + spacingW; + // Height is max of text line height and icon + const float textH = (prefixSz.y > msgSz.y ? prefixSz.y : msgSz.y); + const float textH2 = (suffixSz.y > textH ? suffixSz.y : textH); + contentH = (textH2 > contentH) ? textH2 : contentH; + + const ImVec2 boxSize(contentW + boxPaddingX * 2.0f, contentH + boxPaddingY * 2.0f); + + // Compute box position based on corner + stacking + ImVec2 boxPos = basePosition; + + const bool rightAligned = (position == 1 || position == 3); + const bool bottomAligned = (position == 2 || position == 3); + + if (rightAligned) { + boxPos.x -= boxSize.x; + } + if (bottomAligned) { + boxPos.y -= boxSize.y; + boxPos.y -= stackOffsetY; + } else { + boxPos.y += stackOffsetY; } - ImGui::SetWindowPos(notificationPos); + ImVec2 boxPosMax(boxPos.x + boxSize.x, boxPos.y + boxSize.y); - if (notification.itemIcon != nullptr) { - ImGui::Image(Ship::Context::GetInstance()->GetWindow()->GetGui()->GetTextureByName(notification.itemIcon), - ImVec2(24, 24)); - ImGui::SameLine(); + // Notification.cpp + float bgOpacity = CVarGetFloat(CVAR_SETTING("Notifications.BgOpacity"), 0.5f); + bgOpacity = ImClamp(bgOpacity, 0.0f, 1.0f); + + ImU32 bgCol = ImGui::GetColorU32(ImVec4(0.0f, 0.0f, 0.0f, bgOpacity * alpha)); + dl->AddRectFilled(boxPos, boxPosMax, bgCol, rounding); + + // Draw contents + ImVec2 cursor = ImVec2(boxPos.x + boxPaddingX, boxPos.y + boxPaddingY); + + if (hasIcon) { + ImTextureID tex = Ship::Context::GetInstance()->GetWindow()->GetGui()->GetTextureByName(n.itemIcon); + ImU32 iconTint = ImGui::GetColorU32(ImVec4(1.0f, 1.0f, 1.0f, alpha)); + dl->AddImage(tex, cursor, ImVec2(cursor.x + iconSize, cursor.y + iconSize), ImVec2(0, 0), ImVec2(1, 1), + iconTint); + cursor.x += iconSize + iconTextSpacing; } - if (!notification.prefix.empty()) { - ImGui::TextColored(notification.prefixColor, "%s", notification.prefix.c_str()); - ImGui::SameLine(); + + // Apply alpha to colors + auto withAlpha = [&](ImVec4 c) { + c.w *= alpha; + return c; + }; + + if (hasPrefix) { + dl->AddText(font, fontSize, cursor, ImGui::GetColorU32(withAlpha(n.prefixColor)), n.prefix.c_str()); + cursor.x += prefixSz.x + segSpacing; } - ImGui::TextColored(notification.messageColor, "%s", notification.message.c_str()); - if (!notification.suffix.empty()) { - ImGui::SameLine(); - ImGui::TextColored(notification.suffixColor, "%s", notification.suffix.c_str()); + + dl->AddText(font, fontSize, cursor, ImGui::GetColorU32(withAlpha(n.messageColor)), n.message.c_str()); + cursor.x += msgSz.x; + + if (hasSuffix) { + cursor.x += segSpacing; + dl->AddText(font, fontSize, cursor, ImGui::GetColorU32(withAlpha(n.suffixColor)), n.suffix.c_str()); } - ImGui::End(); - ImGui::PopStyleVar(); + // Advance stacking + stackOffsetY += boxSize.y + padding; } - - ImGui::PopStyleVar(); - ImGui::PopStyleColor(2); } void Window::UpdateElement() { diff --git a/soh/soh/OTRGlobals.cpp b/soh/soh/OTRGlobals.cpp index 2aadd55d207..9bf1c5bc5db 100644 --- a/soh/soh/OTRGlobals.cpp +++ b/soh/soh/OTRGlobals.cpp @@ -48,6 +48,7 @@ #include "Enhancements/custom-message/CustomMessageManager.h" #include "Enhancements/Presets/Presets.h" #include "util.h" +#include "soh/Enhancements/randomizer/hook_handlers.h" #if not defined(__SWITCH__) && not defined(__WIIU__) #include "Extractor/Extract.h" @@ -127,6 +128,7 @@ #include "soh/config/ConfigUpdaters.h" #include "soh/ShipInit.hpp" +#include "soh/Enhancements/custom-item/CustomItem.h" extern "C" { #include "src/overlays/actors/ovl_En_Dns/z_en_dns.h" @@ -769,6 +771,7 @@ extern "C" void VanillaItemTable_Init() { GET_ITEM(ITEM_BULLET_BAG_50, OBJECT_GI_DEKUPOUCH, GID_BULLET_BAG_50, 0x6C, 0x80, CHEST_ANIM_LONG, ITEM_CATEGORY_LESSER, MOD_NONE, GI_BULLET_BAG_50), GET_ITEM_NONE, GET_ITEM_NONE, + GET_ITEM(ITEM_SHIP, OBJECT_UNSET_16E, GID_MAXIMUM,TEXT_CUSTOM_MESSAGE, 0x80, CHEST_ANIM_LONG, ITEM_CATEGORY_JUNK, MOD_NONE, GI_SHIP), GET_ITEM_NONE // GI_MAX - if you need to add to this table insert it before this entry. // clang-format on }; @@ -1279,6 +1282,22 @@ extern "C" void InitOTR(int argc, char* argv[]) { conf->RegisterVersionUpdater(std::make_shared()); conf->RunVersionUpdates(); + // CVarRegisterInteger(CVAR_SETTING("AltAssets"), 1); + // CVarRegisterInteger(CVAR_GENERAL("LetItSnow"), 1); + // CVarRegisterInteger("gHoliday.Visual.SnowingWeather", 1); + // CVarRegisterInteger("gHoliday.Visual.Hats", 1); + // CVarRegisterInteger("gHoliday.Gameplay.Snowballs", 1); + // CVarRegisterInteger(CVAR_COSMETIC("Hud.AButton.Changed"), 1); + // CVarRegisterColor(CVAR_COSMETIC("Hud.AButton.Value"), Color_RGBA8{ 255, 255, 255, 255 }); + // CVarRegisterInteger(CVAR_COSMETIC("Hud.BButton.Changed"), 1); + // CVarRegisterColor(CVAR_COSMETIC("Hud.BButton.Value"), Color_RGBA8{ 255, 255, 255, 255 }); + // CVarRegisterInteger(CVAR_COSMETIC("Hud.CButtons.Changed"), 1); + // CVarRegisterColor(CVAR_COSMETIC("Hud.CButtons.Value"), Color_RGBA8{ 255, 255, 255, 255 }); + // CVarRegisterInteger(CVAR_COSMETIC("Consumable.Hearts.Changed"), 1); + // CVarRegisterColor(CVAR_COSMETIC("Consumable.Hearts.Value"), Color_RGBA8{ 255, 158, 0, 255 }); + // CVarRegisterInteger(CVAR_COSMETIC("Consumable.Magic.Changed"), 1); + // CVarRegisterColor(CVAR_COSMETIC("Consumable.Magic.Value"), Color_RGBA8{ 255, 0, 0, 255 }); + SohGui::SetupGuiElements(); ShipInit::InitAll(); @@ -1306,6 +1325,9 @@ extern "C" void InitOTR(int argc, char* argv[]) { OTRExtScanner(); VanillaItemTable_Init(); DebugConsole_Init(); + CustomMessageManager::Instance->RegisterHooks(); + GameInteractor::Instance->RegisterOwnHooks(); + CustomItem::RegisterHooks(); InitMods(); ActorDB::AddBuiltInCustomActors(); @@ -1320,11 +1342,6 @@ extern "C" void InitOTR(int argc, char* argv[]) { time_t now = time(NULL); tm* tm_now = localtime(&now); - if (tm_now->tm_mon == 11 && tm_now->tm_mday >= 24 && tm_now->tm_mday <= 25) { - CVarRegisterInteger(CVAR_GENERAL("LetItSnow"), 1); - } else { - CVarClear(CVAR_GENERAL("LetItSnow")); - } srand(now); #ifdef ENABLE_REMOTE_CONTROL @@ -2259,6 +2276,10 @@ extern "C" void Randomizer_ShowRandomizerMenu() { SohGui::ShowRandomizerSettingsMenu(); } +extern "C" void Archipelago_ShowArchipelagoMenu() { + SohGui::ShowArchipelagoSettingsMenu(); +} + CustomMessage Randomizer_GetCustomGetItemMessage(Player* player) { s16 giid; if (player->getItemEntry.objectId != OBJECT_INVALID) { @@ -2281,7 +2302,8 @@ extern "C" int CustomMessage_RetrieveIfExists(PlayState* play) { s16 actorParams = 0; if (IS_RANDO) { auto ctx = Rando::Context::GetInstance(); - if (ctx->GetOption(RSK_SHUFFLE_ENTRANCES)) { + if (ctx->GetOption(RSK_SHUFFLE_ENTRANCES) && + CVarGetInteger(CVAR_RANDOMIZER_ENHANCEMENT("EntrancesOnSigns"), 0)) { s16 entrance = -1; switch (textId) { case TEXT_WATERFALL: @@ -2435,6 +2457,11 @@ extern "C" int CustomMessage_RetrieveIfExists(PlayState* play) { messageEntry = Randomizer::GetIceTrapMessage(); } else if (player->getItemEntry.getItemId == RG_TRIFORCE_PIECE) { messageEntry = Randomizer::GetTriforcePieceMessage(); + } else if (player->getItemEntry.getItemId == RG_ARCHIPELAGO_ITEM_USEFUL || + player->getItemEntry.getItemId == RG_ARCHIPELAGO_ITEM_JUNK || + player->getItemEntry.getItemId == RG_ARCHIPELAGO_ITEM_PROGRESSIVE) { + messageEntry = Randomizer::GetArchipelagoItemMessage(player->getItemEntry.getItemId, + RandomizerReturnCurrentlyQueuedItem()); } else { messageEntry = Randomizer_GetCustomGetItemMessage(player); } @@ -2880,6 +2907,16 @@ bool SoH_HandleConfigDrop(char* filePath) { return false; } +extern "C" void ParseArchipelago() { + OTRGlobals::Instance->gRandoContext->ParseArchipelago(); +} + +extern "C" bool checkArchipelagoSlotInfo(const char* slotName, const char* roomHash) { + const std::string slot = std::string(slotName); + const std::string room = std::string(roomHash); + return ArchipelagoClient::GetInstance().slotMatch(slot, room); +} + extern "C" void CheckTracker_RecalculateAvailableChecks() { CheckTracker::RecalculateAvailableChecks(); } diff --git a/soh/soh/OTRGlobals.h b/soh/soh/OTRGlobals.h index c7c5702a0ac..9b971401701 100644 --- a/soh/soh/OTRGlobals.h +++ b/soh/soh/OTRGlobals.h @@ -155,6 +155,7 @@ uint8_t Randomizer_IsSpoilerLoaded(); void Randomizer_SetSpoilerLoaded(bool spoilerLoaded); uint8_t Randomizer_GenerateRandomizer(); void Randomizer_ShowRandomizerMenu(); +void Archipelago_ShowArchipelagoMenu(); int CustomMessage_RetrieveIfExists(PlayState* play); GetItemEntry ItemTable_Retrieve(int16_t getItemID); GetItemEntry ItemTable_RetrieveEntry(s16 modIndex, s16 getItemID); @@ -169,6 +170,8 @@ void CheckTracker_RecalculateAvailableChecks(); GetItemID RetrieveGetItemIDFromItemID(ItemID itemID); RandomizerGet RetrieveRandomizerGetFromItemID(ItemID itemID); +void ParseArchipelago(); +bool checkArchipelagoSlotInfo(const char* slotName, const char* roomHash); void Messagebox_ShowErrorBox(char* title, char* body); #endif diff --git a/soh/soh/ResourceManagerHelpers.cpp b/soh/soh/ResourceManagerHelpers.cpp index 127378da770..b3595e1610a 100644 --- a/soh/soh/ResourceManagerHelpers.cpp +++ b/soh/soh/ResourceManagerHelpers.cpp @@ -357,6 +357,19 @@ extern "C" void ResourceMgr_PatchGfxByName(const char* path, const char* patchNa *gfx = instruction; } +extern "C" void ResourceMgr_PatchCustomGfxByName(const char* path, const char* patchName, int index, Gfx instruction) { + auto res = std::static_pointer_cast( + Ship::Context::GetInstance()->GetResourceManager()->LoadResource(path)); + + Gfx* gfx = (Gfx*)&res->Instructions[index]; + + if (!originalGfx.contains(path) || !originalGfx[path].contains(patchName)) { + originalGfx[path][patchName] = { index, *gfx }; + } + + *gfx = instruction; +} + extern "C" void ResourceMgr_PatchGfxCopyCommandByName(const char* path, const char* patchName, int destinationIndex, int sourceIndex) { auto res = std::static_pointer_cast( diff --git a/soh/soh/ResourceManagerHelpers.h b/soh/soh/ResourceManagerHelpers.h index 8562a3e0e35..afd204deac1 100644 --- a/soh/soh/ResourceManagerHelpers.h +++ b/soh/soh/ResourceManagerHelpers.h @@ -47,6 +47,7 @@ Gfx* ResourceMgr_LoadGfxByCRC(uint64_t crc); Gfx* ResourceMgr_LoadGfxByName(const char* path); uint8_t ResourceMgr_FileIsCustomByName(const char* path); void ResourceMgr_PatchGfxByName(const char* path, const char* patchName, int index, Gfx instruction); +void ResourceMgr_PatchCustomGfxByName(const char* path, const char* patchName, int index, Gfx instruction); void ResourceMgr_UnpatchGfxByName(const char* path, const char* patchName); char* ResourceMgr_LoadArrayByNameAsVec3s(const char* path); Vtx* ResourceMgr_LoadVtxByCRC(uint64_t crc); diff --git a/soh/soh/SaveManager.cpp b/soh/soh/SaveManager.cpp index 9317f8cbc4e..b915302a57c 100644 --- a/soh/soh/SaveManager.cpp +++ b/soh/soh/SaveManager.cpp @@ -113,6 +113,8 @@ SaveManager::SaveManager() { coreSectionIDsByName["entrances"] = SECTION_ID_ENTRANCES; coreSectionIDsByName["scenes"] = SECTION_ID_SCENES; coreSectionIDsByName["trackerData"] = SECTION_ID_TRACKER_DATA; + coreSectionIDsByName["archipelagoData"] = SECTION_ID_ARCHIPELAGO; + coreSectionIDsByName["rogueLike"] = SECTION_ID_ROGUELIKE; AddLoadFunction("base", 1, LoadBaseVersion1); AddLoadFunction("base", 2, LoadBaseVersion2); AddLoadFunction("base", 3, LoadBaseVersion3); @@ -121,6 +123,8 @@ SaveManager::SaveManager() { AddLoadFunction("randomizer", 1, LoadRandomizer); AddSaveFunction("randomizer", 1, SaveRandomizer, true, SECTION_PARENT_NONE); + AddLoadFunction("rogueLike", 1, LoadRogueLike); + AddSaveFunction("rogueLike", 1, SaveRogueLike, true, SECTION_PARENT_NONE); AddInitFunction(InitFileImpl); @@ -145,6 +149,7 @@ SaveManager::SaveManager() { } info.randoSave = 0; + info.archiSave = 0; info.requiresMasterQuest = 0; info.requiresOriginal = 0; @@ -152,6 +157,9 @@ SaveManager::SaveManager() { info.buildVersionMinor = 0; info.buildVersionPatch = 0; memset(&info.buildVersion, 0, sizeof(info.buildVersion)); + + memset(&info.archiUri, 0, sizeof(info.archiUri)); + memset(&info.slotName, 0, sizeof(info.slotName)); } } @@ -409,6 +417,36 @@ void SaveManager::SaveRandomizer(SaveContext* saveContext, int sectionID, bool f }); } +void SaveManager::LoadRogueLike() { + if (gSaveContext.ship.quest.id != QUEST_ROGUELIKE) { + return; + } + + SaveManager::Instance->LoadData("difficulty", gSaveContext.ship.quest.data.rogueLike.difficulty); + SaveManager::Instance->LoadData("lastActivity", gSaveContext.ship.quest.data.rogueLike.lastActivity); + SaveManager::Instance->LoadData("xp", gSaveContext.ship.quest.data.rogueLike.xp); + + SaveManager::Instance->LoadArray("stats", ARRAY_COUNT(gSaveContext.ship.quest.data.rogueLike.stats), [&](size_t i) { + u32 value = 0; + SaveManager::Instance->LoadData("", value); + gSaveContext.ship.quest.data.rogueLike.stats[i] = value; + }); +} + +void SaveManager::SaveRogueLike(SaveContext* saveContext, int sectionID, bool fullSave) { + if (saveContext->ship.quest.id != QUEST_ROGUELIKE) { + return; + } + + SaveManager::Instance->SaveData("difficulty", saveContext->ship.quest.data.rogueLike.difficulty); + SaveManager::Instance->SaveData("lastActivity", saveContext->ship.quest.data.rogueLike.lastActivity); + SaveManager::Instance->SaveData("xp", saveContext->ship.quest.data.rogueLike.xp); + + SaveManager::Instance->SaveArray("stats", ARRAY_COUNT(saveContext->ship.quest.data.rogueLike.stats), [&](size_t i) { + SaveManager::Instance->SaveData("", saveContext->ship.quest.data.rogueLike.stats[i]); + }); +} + // Init() here is an extension of InitSram, and thus not truly an initializer for SaveManager itself. don't put any // class initialization stuff here void SaveManager::Init() { @@ -609,7 +647,8 @@ void SaveManager::InitMeta(int fileNum) { fileMetaInfo[fileNum].seedHash[i] = randoContext->hashIconIndexes[i]; } - fileMetaInfo[fileNum].randoSave = IS_RANDO; + fileMetaInfo[fileNum].randoSave = IS_RANDO && !IS_ARCHIPELAGO; + fileMetaInfo[fileNum].archiSave = IS_ARCHIPELAGO; // If the file is marked as a Master Quest file or if we're randomized and have at least one master quest dungeon, // we need the mq otr. fileMetaInfo[fileNum].requiresMasterQuest = @@ -619,11 +658,24 @@ void SaveManager::InitMeta(int fileNum) { fileMetaInfo[fileNum].requiresOriginal = !IS_MASTER_QUEST && (!IS_RANDO || randoContext->GetDungeons()->CountMQ() < 12); + if (IS_ROGUELIKE) { // IDK + fileMetaInfo[fileNum].requiresMasterQuest = false; + fileMetaInfo[fileNum].requiresOriginal = false; + } + fileMetaInfo[fileNum].buildVersionMajor = gSaveContext.ship.stats.buildVersionMajor; fileMetaInfo[fileNum].buildVersionMinor = gSaveContext.ship.stats.buildVersionMinor; fileMetaInfo[fileNum].buildVersionPatch = gSaveContext.ship.stats.buildVersionPatch; SohUtils::CopyStringToCharArray(fileMetaInfo[fileNum].buildVersion, gSaveContext.ship.stats.buildVersion, ARRAY_COUNT(fileMetaInfo[fileNum].buildVersion)); + + SohUtils::CopyStringToCharArray(fileMetaInfo[fileNum].archiUri, gSaveContext.ship.quest.data.archipelago.archiUri, + ARRAY_COUNT(fileMetaInfo[fileNum].archiUri)); + SohUtils::CopyStringToCharArray(fileMetaInfo[fileNum].slotName, gSaveContext.ship.quest.data.archipelago.slotName, + ARRAY_COUNT(fileMetaInfo[fileNum].slotName)); + SohUtils::CopyStringToCharArray(fileMetaInfo[fileNum].archiRoomSeed, + gSaveContext.ship.quest.data.archipelago.roomHash, + ARRAY_COUNT(fileMetaInfo[fileNum].archiRoomSeed)); } void SaveManager::InitFile(bool isDebug) { @@ -2136,6 +2188,9 @@ void SaveManager::LoadBaseVersion4() { SaveManager::Instance->LoadData("dogParams", gSaveContext.dogParams); SaveManager::Instance->LoadData("filenameLanguage", gSaveContext.ship.filenameLanguage); SaveManager::Instance->LoadData("maskMemory", gSaveContext.ship.maskMemory); + + // Ugh.. + SaveManager::Instance->LoadData("questId", gSaveContext.ship.quest.id); } void SaveManager::SaveBase(SaveContext* saveContext, int sectionID, bool fullSave) { @@ -2304,6 +2359,9 @@ void SaveManager::SaveBase(SaveContext* saveContext, int sectionID, bool fullSav SaveManager::Instance->SaveData("dogParams", saveContext->dogParams); SaveManager::Instance->SaveData("filenameLanguage", saveContext->ship.filenameLanguage); SaveManager::Instance->SaveData("maskMemory", saveContext->ship.maskMemory); + + // Ugh.. + SaveManager::Instance->SaveData("questId", gSaveContext.ship.quest.id); } // Load a string into a char array based on size and ensuring it is null terminated when overflowed @@ -2404,6 +2462,7 @@ void SaveManager::CopyZeldaFile(int from, int to) { fileMetaInfo[to].defense = fileMetaInfo[from].defense; fileMetaInfo[to].health = fileMetaInfo[from].health; fileMetaInfo[to].randoSave = fileMetaInfo[from].randoSave; + fileMetaInfo[to].archiSave = fileMetaInfo[from].archiSave; fileMetaInfo[to].requiresMasterQuest = fileMetaInfo[from].requiresMasterQuest; fileMetaInfo[to].requiresOriginal = fileMetaInfo[from].requiresOriginal; fileMetaInfo[to].buildVersionMajor = fileMetaInfo[from].buildVersionMajor; @@ -2412,6 +2471,7 @@ void SaveManager::CopyZeldaFile(int from, int to) { fileMetaInfo[to].filenameLanguage = fileMetaInfo[from].filenameLanguage; SohUtils::CopyStringToCharArray(fileMetaInfo[to].buildVersion, fileMetaInfo[from].buildVersion, ARRAY_COUNT(fileMetaInfo[to].buildVersion)); + GameInteractor::Instance->ExecuteHooks(from, to); } void SaveManager::DeleteZeldaFile(int fileNum) { @@ -2420,6 +2480,7 @@ void SaveManager::DeleteZeldaFile(int fileNum) { } fileMetaInfo[fileNum].valid = false; fileMetaInfo[fileNum].randoSave = false; + fileMetaInfo[fileNum].archiSave = false; fileMetaInfo[fileNum].requiresMasterQuest = false; fileMetaInfo[fileNum].requiresOriginal = false; GameInteractor::Instance->ExecuteHooks(fileNum); diff --git a/soh/soh/SaveManager.h b/soh/soh/SaveManager.h index 42c60884447..a3639b9cd8b 100644 --- a/soh/soh/SaveManager.h +++ b/soh/soh/SaveManager.h @@ -16,6 +16,7 @@ typedef struct { u32 requiresOriginal; u8 seedHash[5]; u8 randoSave; + u8 archiSave; char buildVersion[50]; s16 buildVersionMajor; s16 buildVersionMinor; @@ -32,6 +33,10 @@ typedef struct { s32 filenameLanguage; s32 gregFound; s32 hasWallet; + + char archiRoomSeed[100]; + char slotName[17]; + char archiUri[50]; } SaveFileMetaInfo; typedef enum { @@ -169,6 +174,8 @@ class SaveManager { static void LoadRandomizer(); static void SaveRandomizer(SaveContext* saveContext, int sectionID, bool fullSave); + static void LoadRogueLike(); + static void SaveRogueLike(SaveContext* saveContext, int sectionID, bool fullSave); static void LoadBaseVersion1(); static void LoadBaseVersion2(); diff --git a/soh/soh/SohGui/Menu.cpp b/soh/soh/SohGui/Menu.cpp index 592c0c5e670..b80b6410861 100644 --- a/soh/soh/SohGui/Menu.cpp +++ b/soh/soh/SohGui/Menu.cpp @@ -463,6 +463,15 @@ void Menu::MenuDrawItem(WidgetInfo& widget, uint32_t width, UIWidgets::Colors me } } } break; + case WIDGET_CVAR_BTN_SELECTOR: { + auto options = std::static_pointer_cast(widget.options); + options->color = menuThemeIndex; + if (UIWidgets::CVarBtnSelector(widget.name.c_str(), widget.cVar, *options)) { + if (widget.callback != nullptr) { + widget.callback(widget); + } + } + } break; case WIDGET_BUTTON: { auto options = std::static_pointer_cast(widget.options); options->color = menuThemeIndex; diff --git a/soh/soh/SohGui/MenuTypes.h b/soh/soh/SohGui/MenuTypes.h index 6dd9aca2c5d..3269d80faf1 100644 --- a/soh/soh/SohGui/MenuTypes.h +++ b/soh/soh/SohGui/MenuTypes.h @@ -3,6 +3,7 @@ #include #include "UIWidgets.hpp" +#include "soh/Enhancements/cvars/CvarCatalog.h" typedef enum { DISABLE_FOR_NO_VSYNC, @@ -38,6 +39,7 @@ typedef enum { WIDGET_CVAR_COMBOBOX, WIDGET_CVAR_SLIDER_INT, WIDGET_CVAR_SLIDER_FLOAT, + WIDGET_CVAR_BTN_SELECTOR, WIDGET_BUTTON, WIDGET_INPUT, WIDGET_CVAR_INPUT, @@ -72,10 +74,10 @@ typedef enum { // holds the widget values for a widget, contains all CVar types available from LUS. int32_t is used for boolean // evaluation using CVarVariant = std::variant; -using OptionsVariant = - std::variant; +using OptionsVariant = std::variant; // All the info needed for display and search of all widgets in the menu. // `name` is the label displayed, @@ -116,6 +118,12 @@ struct WidgetInfo { WidgetInfo& CVar(const char* cVar_) { cVar = cVar_; + + // Auto-register ONLY checkbox-backed CVars for the hotkey picker list + if (type == WIDGET_CVAR_CHECKBOX) { + CVarCatalog::Register(cVar_, name.c_str()); + } + return *this; } @@ -136,6 +144,10 @@ struct WidgetInfo { options = std::make_shared(std::get(options_)); break; + case WIDGET_CVAR_BTN_SELECTOR: + options = + std::make_shared(std::get(options_)); + break; case WIDGET_SLIDER_INT: case WIDGET_CVAR_SLIDER_INT: options = diff --git a/soh/soh/SohGui/SohGui.cpp b/soh/soh/SohGui/SohGui.cpp index 172f923e47f..96ef5c8129e 100644 --- a/soh/soh/SohGui/SohGui.cpp +++ b/soh/soh/SohGui/SohGui.cpp @@ -26,15 +26,18 @@ #include "soh/OTRGlobals.h" #include "soh/Enhancements/Presets/Presets.h" #include "soh/resource/type/Skeleton.h" -#include "libultraship/libultraship.h" #include "soh/Enhancements/game-interactor/GameInteractor.h" #include "soh/Enhancements/cosmetics/authenticGfxPatches.h" #include "soh/Enhancements/debugger/MessageViewer.h" #include "soh/Notification/Notification.h" #include "soh/Enhancements/TimeDisplay/TimeDisplay.h" +#include "soh/Network/Archipelago/ArchipelagoSettingsWindow.h" +#include "soh/Network/Archipelago/ArchipelagoConsoleWindow.h" #include "soh/Enhancements/mod_menu.h" #include "soh/Network/Anchor/Anchor.h" +#include "soh/Network/CrowdControl/CrowdControl.h" +#include "soh/Enhancements/Holiday/Caladius.h" namespace SohGui { @@ -95,11 +98,16 @@ std::shared_ptr mItemTrackerSettingsWindow; std::shared_ptr mItemTrackerWindow; std::shared_ptr mTimeSplitWindow; std::shared_ptr mPlandomizerWindow; +std::shared_ptr mArchipelagoSettingsWindow; +std::shared_ptr mArchipelagoConsoleWindow; std::shared_ptr mRandomizerSettingsWindow; std::shared_ptr mModalWindow; std::shared_ptr mNotificationWindow; std::shared_ptr mTimeDisplayWindow; std::shared_ptr mAnchorRoomWindow; +std::shared_ptr mCrowdControlChaosWindow; +std::shared_ptr mCrowdControlEffectTimersWindow; +std::shared_ptr mCaladiusWindow; UIWidgets::Colors GetMenuThemeColor() { return mSohMenu->GetMenuThemeColor(); @@ -199,6 +207,12 @@ void SetupGuiElements() { mPlandomizerWindow = std::make_shared(CVAR_WINDOW("PlandomizerEditor"), "Plandomizer Editor", ImVec2(850, 760)); gui->AddGuiWindow(mPlandomizerWindow); + mArchipelagoSettingsWindow = std::make_shared(CVAR_WINDOW("ArchipelagoSettingsWindow"), + "Archipelago Settings", ImVec2(600, 450)); + gui->AddGuiWindow(mArchipelagoSettingsWindow); + mArchipelagoConsoleWindow = std::make_shared(CVAR_WINDOW("ArchipelagoConsoleWindow"), + "Archipelago Console", ImVec2(600, 550)); + gui->AddGuiWindow(mArchipelagoConsoleWindow); mModalWindow = std::make_shared(CVAR_WINDOW("ModalWindow"), "Modal Window"); gui->AddGuiWindow(mModalWindow); mModalWindow->Show(); @@ -209,6 +223,20 @@ void SetupGuiElements() { gui->AddGuiWindow(mTimeDisplayWindow); mAnchorRoomWindow = std::make_shared(CVAR_WINDOW("AnchorRoom"), "Anchor Room"); gui->AddGuiWindow(mAnchorRoomWindow); + + // Offline Crowd Control Chaos window (should stay open when ESC menu closes) + mCrowdControlChaosWindow = + std::make_shared(CVAR_WINDOW("CrowdControlChaos"), "Crowd Control Chaos"); + gui->AddGuiWindow(mCrowdControlChaosWindow); + // Crowd Control Timers window (stays open when ESC menu closes) + mCrowdControlEffectTimersWindow = std::make_shared( + CVAR_WINDOW("CrowdControlTimers"), "Crowd Control Timers", ImVec2(420, 240) // <-- add a real default size + ); + gui->AddGuiWindow(mCrowdControlEffectTimersWindow); + + mCaladiusWindow = std::make_shared(CVAR_WINDOW("Holiday Cal"), "Holiday Cal"); + gui->AddGuiWindow(mCaladiusWindow); + mCaladiusWindow->Show(); } void Destroy() { @@ -243,8 +271,13 @@ void Destroy() { mInputViewerSettings = nullptr; mTimeSplitWindow = nullptr; mPlandomizerWindow = nullptr; + mArchipelagoSettingsWindow = nullptr; + mArchipelagoConsoleWindow = nullptr; mTimeDisplayWindow = nullptr; mAnchorRoomWindow = nullptr; + mCrowdControlChaosWindow = nullptr; + mCrowdControlEffectTimersWindow = nullptr; + mCaladiusWindow = nullptr; } void RegisterPopup(std::string title, std::string message, std::string button1, std::string button2, @@ -255,4 +288,12 @@ void RegisterPopup(std::string title, std::string message, std::string button1, void ShowRandomizerSettingsMenu() { mRandomizerSettingsWindow->Show(); } + +void ShowArchipelagoSettingsMenu() { + mArchipelagoSettingsWindow->Show(); +} + +void ShowEscMenu() { + mSohMenu->Show(); +} } // namespace SohGui diff --git a/soh/soh/SohGui/SohGui.hpp b/soh/soh/SohGui/SohGui.hpp index d9b7bb64415..77505e92211 100644 --- a/soh/soh/SohGui/SohGui.hpp +++ b/soh/soh/SohGui/SohGui.hpp @@ -29,6 +29,7 @@ #include "soh/Enhancements/randomizer/randomizer_settings_window.h" #include "soh/Enhancements/timesplits/TimeSplits.h" #include "soh/Enhancements/randomizer/Plandomizer.h" +#include "soh/Network/Archipelago/Archipelago.h" #include "SohModals.h" namespace SohGui { @@ -39,6 +40,8 @@ void Destroy(); void RegisterPopup(std::string title, std::string message, std::string button1 = "OK", std::string button2 = "", std::function button1callback = nullptr, std::function button2callback = nullptr); void ShowRandomizerSettingsMenu(); +void ShowArchipelagoSettingsMenu(); +void ShowEscMenu(); UIWidgets::Colors GetMenuThemeColor(); } // namespace SohGui diff --git a/soh/soh/SohGui/SohMenu.cpp b/soh/soh/SohGui/SohMenu.cpp index c5153074e83..f4c7f505ea0 100644 --- a/soh/soh/SohGui/SohMenu.cpp +++ b/soh/soh/SohGui/SohMenu.cpp @@ -45,6 +45,9 @@ WidgetInfo& SohMenu::AddWidget(WidgetPath& pathInfo, std::string widgetName, Wid case WIDGET_CVAR_SLIDER_FLOAT: widget.options = std::make_shared(); break; + case WIDGET_CVAR_BTN_SELECTOR: + widget.options = std::make_shared(); + break; case WIDGET_SLIDER_INT: case WIDGET_CVAR_SLIDER_INT: widget.options = std::make_shared(); @@ -86,6 +89,9 @@ void SohMenu::InitElement() { AddMenuSettings(); AddMenuEnhancements(); AddMenuRandomizer(); + AddMenuEntry("Holiday", CVAR_SETTING("Menu.HolidaySidebarSection")); + AddSidebarEntry("Holiday", "Gameplay", 2); + AddSidebarEntry("Holiday", "Visual", 2); AddMenuNetwork(); AddMenuDevTools(); diff --git a/soh/soh/SohGui/SohMenuDevTools.cpp b/soh/soh/SohGui/SohMenuDevTools.cpp index 288d002f2d4..a0e81268852 100644 --- a/soh/soh/SohGui/SohMenuDevTools.cpp +++ b/soh/soh/SohGui/SohMenuDevTools.cpp @@ -45,6 +45,18 @@ void SohMenu::AddMenuDevTools() { .Options( CheckboxOptions().Tooltip("Automatically shows Debug Warp Screen when starting or resetting the game.\n" "This option takes precedence over \"Boot Sequence\" option.")); + AddWidget(path, "Map Select Button Combination:", WIDGET_CVAR_BTN_SELECTOR) + .CVar("gDeveloperTools.MapSelectBtn") + .Options(BtnSelectorOptions().DefaultValue(BTN_R | BTN_L | BTN_Z)) + .PreFunc([](WidgetInfo& info) { info.isHidden = !CVarGetInteger(CVAR_DEVELOPER_TOOLS("DebugEnabled"), 0); }); + AddWidget(path, "No Clip Button Combination:", WIDGET_CVAR_BTN_SELECTOR) + .CVar("gDeveloperTools.NoClipBtn") + .PreFunc([](WidgetInfo& info) { info.isHidden = !CVarGetInteger(CVAR_DEVELOPER_TOOLS("DebugEnabled"), 0); }) + .Options(BtnSelectorOptions().DefaultValue(BTN_L | BTN_DRIGHT)); + AddWidget(path, "Void Button Combination:", WIDGET_CVAR_BTN_SELECTOR) + .CVar("gDeveloperTools.VoidBtn") + .PreFunc([](WidgetInfo& info) { info.isHidden = !CVarGetInteger(CVAR_DEVELOPER_TOOLS("DebugEnabled"), 0); }) + .Options(BtnSelectorOptions().DefaultValue(0)); AddWidget(path, "OoT Registry Editor", WIDGET_CVAR_CHECKBOX) .CVar(CVAR_DEVELOPER_TOOLS("RegEditEnabled")) .PreFunc([](WidgetInfo& info) { info.isHidden = !CVarGetInteger(CVAR_DEVELOPER_TOOLS("DebugEnabled"), 0); }) diff --git a/soh/soh/SohGui/SohMenuEnhancements.cpp b/soh/soh/SohGui/SohMenuEnhancements.cpp index c32b1175587..50eb41eb333 100644 --- a/soh/soh/SohGui/SohMenuEnhancements.cpp +++ b/soh/soh/SohGui/SohMenuEnhancements.cpp @@ -142,6 +142,14 @@ static const std::unordered_map enemyRandomizerModes = { { ENEMY_RANDOMIZER_RANDOM_SEEDED, "Random (Seeded)" }, }; +#define TELEPORT_MODE_SIMPLE 0 +#define TELEPORT_MODE_ADVANCED 1 + +static const std::unordered_map teleportTrapModes = { + { TELEPORT_MODE_SIMPLE, "Simple" }, + { TELEPORT_MODE_ADVANCED, "Advanced" }, +}; + void SohMenu::AddMenuEnhancements() { // Add Enhancements Menu AddMenuEntry("Enhancements", CVAR_SETTING("Menu.EnhancementsSidebarSection")); @@ -884,6 +892,12 @@ void SohMenu::AddMenuEnhancements() { .CVar(CVAR_ENHANCEMENT("BowReticle")) .Options(CheckboxOptions().Tooltip("Aiming with a Bow or Slingshot will display a reticle as with the Hookshot " "when the projectile is ready to fire.")); + AddWidget(path, "Arrow Cycle", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("BowArrowCycle")) + .Options(CheckboxOptions().Tooltip( + "Allows cycling between different arrow types (Normal, Fire, Ice, Light) while aiming the bow. " + "Press the R button to cycle to the next available arrow type. " + "Only works when aiming and only cycles to arrow types you own with sufficient magic.")); path.column = SECTION_COLUMN_3; AddWidget(path, "Hookshot", WIDGET_SEPARATOR_TEXT); @@ -1512,6 +1526,11 @@ void SohMenu::AddMenuEnhancements() { AddSidebarEntry("Enhancements", path.sidebarName, 3); path.column = SECTION_COLUMN_1; + AddWidget(path, "Bounce off Walls", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("BounceOffWalls")) + .Options( + CheckboxOptions().Tooltip("Allows Link to bounce off walls when linear velocity is high enough, this is " + "relevant when frequently being knocked back by traps, CC, or in Anchor.")); AddWidget(path, "Mirrored World", WIDGET_CVAR_COMBOBOX) .CVar(CVAR_ENHANCEMENT("MirroredWorldMode")) .Options( @@ -1556,15 +1575,17 @@ void SohMenu::AddMenuEnhancements() { .Options(CheckboxOptions().Tooltip("Changes Heart Piece and Heart Container functionality.\n\n" " - Each Heart Container or full Heart Piece reduces Link's Hearts by 1.\n" " - Can be enabled retroactively after a File has already started.")); - AddWidget(path, "Additional Traps", WIDGET_CVAR_CHECKBOX) + path.column = SECTION_COLUMN_2; + AddWidget(path, "Extra Traps", WIDGET_SEPARATOR_TEXT); + AddWidget(path, "Enable Extra Traps", WIDGET_CVAR_CHECKBOX) .CVar(CVAR_ENHANCEMENT("ExtraTraps.Enabled")) .Options(CheckboxOptions().Tooltip("Enables additional Trap variants.")); AddWidget(path, "Trap Options", WIDGET_SEPARATOR_TEXT).PreFunc([](WidgetInfo& info) { info.isHidden = CVarGetInteger(CVAR_ENHANCEMENT("ExtraTraps.Enabled"), 0) == 0; }); - AddWidget(path, "Tier 1 Traps:", WIDGET_TEXT).PreFunc([](WidgetInfo& info) { + /*AddWidget(path, "Tier 1 Traps:", WIDGET_TEXT).PreFunc([](WidgetInfo& info) { info.isHidden = CVarGetInteger(CVAR_ENHANCEMENT("ExtraTraps.Enabled"), 0) == 0; - }); + });*/ AddWidget(path, "Freeze Traps", WIDGET_CVAR_CHECKBOX) .CVar(CVAR_ENHANCEMENT("ExtraTraps.Ice")) .PreFunc( @@ -1577,9 +1598,9 @@ void SohMenu::AddMenuEnhancements() { .CVar(CVAR_ENHANCEMENT("ExtraTraps.Shock")) .PreFunc( [](WidgetInfo& info) { info.isHidden = CVarGetInteger(CVAR_ENHANCEMENT("ExtraTraps.Enabled"), 0) == 0; }); - AddWidget(path, "Tier 2 Traps:", WIDGET_TEXT).PreFunc([](WidgetInfo& info) { + /*AddWidget(path, "Tier 2 Traps:", WIDGET_TEXT).PreFunc([](WidgetInfo& info) { info.isHidden = CVarGetInteger(CVAR_ENHANCEMENT("ExtraTraps.Enabled"), 0) == 0; - }); + });*/ AddWidget(path, "Knockback Traps", WIDGET_CVAR_CHECKBOX) .CVar(CVAR_ENHANCEMENT("ExtraTraps.Knockback")) .PreFunc( @@ -1592,9 +1613,9 @@ void SohMenu::AddMenuEnhancements() { .CVar(CVAR_ENHANCEMENT("ExtraTraps.Bomb")) .PreFunc( [](WidgetInfo& info) { info.isHidden = CVarGetInteger(CVAR_ENHANCEMENT("ExtraTraps.Enabled"), 0) == 0; }); - AddWidget(path, "Tier 3 Traps:", WIDGET_TEXT).PreFunc([](WidgetInfo& info) { + /*AddWidget(path, "Tier 3 Traps:", WIDGET_TEXT).PreFunc([](WidgetInfo& info) { info.isHidden = CVarGetInteger(CVAR_ENHANCEMENT("ExtraTraps.Enabled"), 0) == 0; - }); + });*/ AddWidget(path, "Void Traps", WIDGET_CVAR_CHECKBOX) .CVar(CVAR_ENHANCEMENT("ExtraTraps.Void")) .PreFunc( @@ -1603,6 +1624,10 @@ void SohMenu::AddMenuEnhancements() { .CVar(CVAR_ENHANCEMENT("ExtraTraps.Ammo")) .PreFunc( [](WidgetInfo& info) { info.isHidden = CVarGetInteger(CVAR_ENHANCEMENT("ExtraTraps.Enabled"), 0) == 0; }); + AddWidget(path, "Change Age Trap", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("ExtraTraps.ChangeAge")) + .PreFunc( + [](WidgetInfo& info) { info.isHidden = CVarGetInteger(CVAR_ENHANCEMENT("ExtraTraps.Enabled"), 0) == 0; }); AddWidget(path, "Death Traps", WIDGET_CVAR_CHECKBOX) .CVar(CVAR_ENHANCEMENT("ExtraTraps.Kill")) .PreFunc( @@ -1612,7 +1637,22 @@ void SohMenu::AddMenuEnhancements() { .PreFunc( [](WidgetInfo& info) { info.isHidden = CVarGetInteger(CVAR_ENHANCEMENT("ExtraTraps.Enabled"), 0) == 0; }); - path.column = SECTION_COLUMN_2; + AddWidget(path, "Teleport Trap Mode", WIDGET_CVAR_COMBOBOX) + .CVar(CVAR_ENHANCEMENT("ExtraTraps.TeleportMode")) + .Options(ComboboxOptions() + .DefaultIndex(TELEPORT_MODE_SIMPLE) + .ComboMap(teleportTrapModes) + .Tooltip("Controls how Teleport Traps choose their destination:\n\n" + " - Simple: Uses the standard warp song, Link's House, and Temple of Time.\n" + " - Advanced: Teleports to a random entrance from a large pool of exits, " + "including overworld exits, interiors, and boss doors.\n")) + .PreFunc([](WidgetInfo& info) { + const bool trapsOn = CVarGetInteger(CVAR_ENHANCEMENT("ExtraTraps.Enabled"), 0) != 0; + const bool tpOn = CVarGetInteger(CVAR_ENHANCEMENT("ExtraTraps.Teleport"), 0) != 0; + info.isHidden = !trapsOn || !tpOn; + }); + + path.column = SECTION_COLUMN_3; AddWidget(path, "Enemy Randomizer", WIDGET_CVAR_COMBOBOX) .CVar(CVAR_ENHANCEMENT("RandomizedEnemies")) .Callback([](WidgetInfo& info) { GetSelectedEnemies(); }) @@ -1868,7 +1908,7 @@ void SohMenu::AddMenuEnhancements() { .RaceDisable(false) .WindowName("Additional Timers") .Options(WindowButtonOptions().Tooltip("Enables the separate Additional Timers Window.")); - AddWidget(path, "Font Scale: %.2fx", WIDGET_CVAR_SLIDER_FLOAT) + AddWidget(path, "Global Scale: %.2fx", WIDGET_CVAR_SLIDER_FLOAT) .CVar(CVAR_TIME_DISPLAY("FontScale")) .RaceDisable(false) .Callback([](WidgetInfo& info) { TimeDisplayInitSettings(); }) @@ -1877,6 +1917,18 @@ void SohMenu::AddMenuEnhancements() { .CVar(CVAR_TIME_DISPLAY("ShowWindowBG")) .RaceDisable(false) .Callback([](WidgetInfo& info) { TimeDisplayInitSettings(); }); + + AddWidget(path, "Gameplay Timer Options", WIDGET_SEPARATOR_TEXT); + AddWidget(path, "Separate Gameplay Timer", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_TIME_DISPLAY("SeparateGameplay")) + .RaceDisable(false) + .Callback([](WidgetInfo& info) { TimeDisplayInitSettings(); }); + AddWidget(path, "Scale: %.2fx", WIDGET_CVAR_SLIDER_FLOAT) + .CVar(CVAR_TIME_DISPLAY("GameplayFontScale")) + .RaceDisable(false) + .Callback([](WidgetInfo& info) { TimeDisplayInitSettings(); }) + .Options(FloatSliderOptions().Min(1.0f).Max(5.0f).DefaultValue(1.0f).Format("%.2fx")); + AddWidget(path, "Display Options", WIDGET_SEPARATOR_TEXT); for (auto& timer : timeDisplayList) { AddWidget(path, timer.timeLabel, WIDGET_CVAR_CHECKBOX) .RaceDisable(false) diff --git a/soh/soh/SohGui/SohMenuNetwork.cpp b/soh/soh/SohGui/SohMenuNetwork.cpp index 716e4652cb2..68f946bf52d 100644 --- a/soh/soh/SohGui/SohMenuNetwork.cpp +++ b/soh/soh/SohGui/SohMenuNetwork.cpp @@ -28,6 +28,22 @@ void SohMenu::AddMenuNetwork() { return; #endif + // Archipelago + path = { "Network", "Archipelago", SECTION_COLUMN_1 }; + AddSidebarEntry(path.sectionName, path.sidebarName, 2); + AddWidget(path, "Popout Archipelago Settings Window", WIDGET_WINDOW_BUTTON) + .CVar(CVAR_WINDOW("ArchipelagoSettings")) + .RaceDisable(false) + .WindowName("Archipelago Settings") + .Options(WindowButtonOptions().Tooltip("Enables the Archipelago Settings Window.")); + + path.column = SECTION_COLUMN_2; + AddWidget(path, "Popout Archipelago Console Window", WIDGET_WINDOW_BUTTON) + .CVar(CVAR_WINDOW("ArchipelagoConsole")) + .RaceDisable(false) + .WindowName("Archipelago Console") + .Options(WindowButtonOptions().Tooltip("Enables the Archipelago Console Window.")); + // Sail path = { "Network", "Sail", SECTION_COLUMN_1 }; AddSidebarEntry("Network", path.sidebarName, 3); @@ -105,7 +121,7 @@ void SohMenu::AddMenuNetwork() { }); path.sidebarName = "Crowd Control"; - AddSidebarEntry("Network", path.sidebarName, 3); + AddSidebarEntry("Network", path.sidebarName, 2); path.column = SECTION_COLUMN_1; AddWidget(path, "About Crowd Control", WIDGET_SEPARATOR_TEXT); @@ -180,6 +196,16 @@ void SohMenu::AddMenuNetwork() { .RaceDisable(true) .Options(CheckboxOptions().Tooltip("Enemies spawned by CrowdControl won't be considered for \"clear enemy " "rooms\", so they don't need to be killed to complete these rooms.")); + + // Offline Chaos Mode (no Crowd Control connection required) + path.column = SECTION_COLUMN_2; + AddWidget(path, "Chaos Mode", WIDGET_SEPARATOR_TEXT); + AddWidget(path, "Popout Chaos Window", WIDGET_WINDOW_BUTTON) + .CVar(CVAR_WINDOW("CrowdControlChaos")) + .RaceDisable(false) + .WindowName("Crowd Control Chaos") + .Options(WindowButtonOptions().Tooltip("Opens the offline Chaos Mode window (no CC connection required).")); + path.sidebarName = "Anchor"; AddSidebarEntry("Network", path.sidebarName, 2); } diff --git a/soh/soh/SohGui/SohMenuRandomizer.cpp b/soh/soh/SohGui/SohMenuRandomizer.cpp index ad688740a58..86fe80f5be2 100644 --- a/soh/soh/SohGui/SohMenuRandomizer.cpp +++ b/soh/soh/SohGui/SohMenuRandomizer.cpp @@ -1,5 +1,7 @@ #include "SohMenu.h" #include "soh/OTRGlobals.h" +#include "soh/Enhancements/SkipGIAnimations.h" +#include "soh/SohGui/SohGui.hpp" namespace SohGui { @@ -9,7 +11,8 @@ using namespace UIWidgets; static const std::unordered_map skipGetItemAnimationOptions = { { SGIA_DISABLED, "Disabled" }, { SGIA_JUNK, "Junk Items" }, - { SGIA_ALL, "All Items" }, + { SGIA_ALL, "All but Ice Traps" }, + { SGIA_ADVANCED, "Advanced" }, }; void SohMenu::AddMenuRandomizer() { @@ -28,6 +31,7 @@ void SohMenu::AddMenuRandomizer() { // Enhancements path.sidebarName = "Enhancements"; AddSidebarEntry("Randomizer", path.sidebarName, 3); + path.column = SECTION_COLUMN_1; AddWidget(path, "Randomizer Enhancements", WIDGET_SEPARATOR_TEXT); AddWidget(path, "Rando-Relevant Navi Hints", WIDGET_CVAR_CHECKBOX) .CVar(CVAR_RANDOMIZER_ENHANCEMENT("RandoRelevantNavi")) @@ -84,20 +88,117 @@ void SohMenu::AddMenuRandomizer() { .Options(CheckboxOptions().Tooltip( "When shuffling boss souls, they'll appear as a simpler model instead of showing the boss' models." "This might make boss souls more distinguishable from a distance, and can help with performance.")); + AddWidget(path, "Signs Hint Entrances", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_RANDOMIZER_ENHANCEMENT("EntrancesOnSigns")) + .Options(CheckboxOptions().Tooltip("If enabled, signs near loading zones will tell you where they lead to.")); + AddWidget(path, "No Junk Notifications", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_RANDOMIZER_ENHANCEMENT("NoJunkNotifications")); + path.column = SECTION_COLUMN_2; AddWidget(path, "Skip Get Item Animations", WIDGET_CVAR_COMBOBOX) .CVar(CVAR_RANDOMIZER_ENHANCEMENT("TimeSavers.SkipGetItemAnimation")) .Options(ComboboxOptions().ComboMap(skipGetItemAnimationOptions).DefaultIndex(SGIA_JUNK)); AddWidget(path, "Item Scale: %.2f", WIDGET_CVAR_SLIDER_FLOAT) .CVar(CVAR_RANDOMIZER_ENHANCEMENT("TimeSavers.SkipGetItemAnimationScale")) .PreFunc([](WidgetInfo& info) { - info.options->disabled = - !CVarGetInteger(CVAR_RANDOMIZER_ENHANCEMENT("TimeSavers.SkipGetItemAnimation"), SGIA_JUNK); + s32 setting = CVarGetInteger(CVAR_RANDOMIZER_ENHANCEMENT("TimeSavers.SkipGetItemAnimation"), SGIA_JUNK); + + // Only enable scale slider when *some* skipping mode is active (Junk / All / Advanced), + // and keep it disabled when set to "None" if you have that mode. + info.options->disabled = (setting == SGIA_DISABLED); info.options->disabledTooltip = "This slider only applies when using the \"Skip Get Item Animations\" option."; }) .Options(FloatSliderOptions().Min(5.0f).Max(15.0f).Format("%.2f").DefaultValue(10.0f).Tooltip( "The size of the item when it is picked up.")); + AddWidget(path, "Advanced Skip GI Categories", WIDGET_SEPARATOR_TEXT).PreFunc([](WidgetInfo& info) { + s32 setting = CVarGetInteger(CVAR_RANDOMIZER_ENHANCEMENT("TimeSavers.SkipGetItemAnimation"), SGIA_JUNK); + // Only show the list when the main combo is set to Advanced + info.isHidden = (setting != SGIA_ADVANCED); + }); + + AddWidget(path, "Advanced Skip GI Bulk Actions", WIDGET_CUSTOM) + .PreFunc([](WidgetInfo& info) { + s32 setting = CVarGetInteger(CVAR_RANDOMIZER_ENHANCEMENT("TimeSavers.SkipGetItemAnimation"), SGIA_JUNK); + info.isHidden = (setting != SGIA_ADVANCED); + }) + .CustomFunction([](WidgetInfo& info) { + PushStyleInput(THEME_COLOR); + // Split available width across two buttons, accounting for spacing + const float avail = ImGui::GetContentRegionAvail().x; + const float spacing = ImGui::GetStyle().ItemSpacing.x; + const float half = (avail - spacing) * 0.5f; + + if (UIWidgets::Button("Skip All", UIWidgets::ButtonOptions() + .Size(ImVec2(half, 0)) + .Tooltip("Enable all Advanced Skip GI categories."))) { + for (int i = 0; i < SKIP_GI_ADVANCED_CATEGORY_COUNT; ++i) { + CVarSetInteger(skipGIAdvancedCVarList[i], 1); + } + Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); + } + + ImGui::SameLine(); + + if (UIWidgets::Button("Clear All", UIWidgets::ButtonOptions() + .Size(ImVec2(half, 0)) + .Tooltip("Disable all Advanced Skip GI categories."))) { + for (int i = 0; i < SKIP_GI_ADVANCED_CATEGORY_COUNT; ++i) { + CVarSetInteger(skipGIAdvancedCVarList[i], 0); + } + Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); + } + PopStyleInput(); + }); + + AddWidget(path, "Advanced Skip GI Category Grid", WIDGET_CUSTOM) + .PreFunc([](WidgetInfo& info) { + s32 setting = CVarGetInteger(CVAR_RANDOMIZER_ENHANCEMENT("TimeSavers.SkipGetItemAnimation"), SGIA_JUNK); + info.isHidden = (setting != SGIA_ADVANCED); + }) + .CustomFunction([](WidgetInfo& info) { + PushStyleInput(THEME_COLOR); + ImGui::PushStyleColor(ImGuiCol_CheckMark, ImVec4(1.0f, 1.0f, 1.0f, 0.75f)); + const float avail = ImGui::GetContentRegionAvail().x; + const float spacing = ImGui::GetStyle().ItemSpacing.x; + const float colW = (avail - spacing) * 0.5f; + + if (ImGui::BeginTable("##SkipGIAdvancedTable", 2, + ImGuiTableFlags_SizingFixedFit | ImGuiTableFlags_NoSavedSettings)) { + + ImGui::TableSetupColumn("##left", ImGuiTableColumnFlags_WidthFixed, colW); + ImGui::TableSetupColumn("##right", ImGuiTableColumnFlags_WidthFixed, colW); + + for (int i = 0; i < SKIP_GI_ADVANCED_CATEGORY_COUNT; i += 2) { + ImGui::TableNextRow(); + + // Left column + ImGui::TableSetColumnIndex(0); + { + bool v = CVarGetInteger(skipGIAdvancedCVarList[i], 0) != 0; + if (ImGui::Checkbox(skipGIAdvancedNameList[i], &v)) { + CVarSetInteger(skipGIAdvancedCVarList[i], v ? 1 : 0); + Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); + } + } + + // Right column (if present) + if (i + 1 < SKIP_GI_ADVANCED_CATEGORY_COUNT) { + ImGui::TableSetColumnIndex(1); + bool v = CVarGetInteger(skipGIAdvancedCVarList[i + 1], 0) != 0; + if (ImGui::Checkbox(skipGIAdvancedNameList[i + 1], &v)) { + CVarSetInteger(skipGIAdvancedCVarList[i + 1], v ? 1 : 0); + Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); + } + } + } + + ImGui::EndTable(); + } + ImGui::PopStyleColor(); + PopStyleInput(); + }); + // Plandomizer path.sidebarName = "Plandomizer"; AddSidebarEntry("Randomizer", path.sidebarName, 1); diff --git a/soh/soh/SohGui/SohMenuSettings.cpp b/soh/soh/SohGui/SohMenuSettings.cpp index 3ea0d8d343e..01326856c04 100644 --- a/soh/soh/SohGui/SohMenuSettings.cpp +++ b/soh/soh/SohGui/SohMenuSettings.cpp @@ -7,6 +7,8 @@ #include "soh/ResourceManagerHelpers.h" #include "UIWidgets.hpp" #include +#include "soh/Enhancements/cvars/CvarCatalog.h" +#include "soh/SohGui/SohGui.hpp" extern "C" { #include "include/z64audio.h" @@ -178,6 +180,12 @@ void SohMenu::AddMenuSettings() { .RaceDisable(false) .Options(CheckboxOptions().Tooltip( "Search input box gets autofocus when visible. Does not affect using other widgets.")); + AddWidget(path, "Settings Menu Button Combination:", WIDGET_CVAR_BTN_SELECTOR) + .CVar("gSettings.SettingsMenuBtn") + .Options(BtnSelectorOptions().DefaultValue(BTN_CUSTOM_MODIFIER1)); + AddWidget(path, "Reset Button Combination:", WIDGET_CVAR_BTN_SELECTOR) + .CVar("gSettings.ResetBtn") + .Options(BtnSelectorOptions().DefaultValue(BTN_CUSTOM_MODIFIER2)); AddWidget(path, "Open App Files Folder", WIDGET_BUTTON) .RaceDisable(false) .Callback([](WidgetInfo& info) { @@ -499,7 +507,7 @@ void SohMenu::AddMenuSettings() { Notification::Emit({ .itemIcon = "__OTR__textures/icon_item_24_static/gQuestIconGoldSkulltulaTex", .prefix = "This", - .message = "is a", + .message = " is a ", .suffix = "test.", }); }) @@ -513,6 +521,245 @@ void SohMenu::AddMenuSettings() { .WindowName("Mod Menu") .HideInSearch(true) .Options(WindowButtonOptions().Tooltip("Enables the separate Mod Menu Window.")); -} + // ========================= + // CVar Binds (checkbox only) + // ========================= + path.sidebarName = "CVar Binds"; + path.column = SECTION_COLUMN_1; + AddSidebarEntry("Settings", path.sidebarName, 1); + + AddWidget(path, "CVar Binds", WIDGET_SEPARATOR_TEXT); + + AddWidget(path, "CVarBindEditor", WIDGET_CUSTOM).HideInSearch(true).CustomFunction([](WidgetInfo& info) { + static ImGuiTextFilter sFilter; + + auto BindCountName = []() -> const char* { return "gSettings.CVarBinds.Count"; }; + + auto BindCVarName = [](int index) -> std::string { + return "gSettings.CVarBinds.Entries.Entry" + std::to_string(index) + ".CVar"; + }; + + auto BindMaskName = [](int index) -> std::string { + return "gSettings.CVarBinds.Entries.Entry" + std::to_string(index) + ".Mask"; + }; + + auto GetCount = [&]() -> int { + int c = CVarGetInteger(BindCountName(), 0); + return c < 0 ? 0 : c; + }; + + auto SetCount = [&](int c) { + if (c < 0) { + c = 0; + } + CVarSetInteger(BindCountName(), c); + }; + + // Build a fast name->label lookup once (avoids scanning the catalog every row) + const auto& all = CVarCatalog::GetAll(); + static std::unordered_map sNameToLabel; + if (sNameToLabel.empty() && !all.empty()) { + sNameToLabel.reserve(all.size()); + for (const auto& e : all) { + sNameToLabel.emplace(e.name, e.label); + } + } + + auto AddBind = [&](const char* cvarName) { + if (cvarName == nullptr || cvarName[0] == '\0') { + return; + } + + // Prevent duplicates + const int count = GetCount(); + for (int i = 0; i < count; i++) { + const std::string cvarKey = BindCVarName(i); + const char* existing = CVarGetString(cvarKey.c_str(), ""); + if (existing && std::string(existing) == cvarName) { + return; + } + } + + const int newIndex = count; + const std::string cvarKey = BindCVarName(newIndex); + const std::string maskKey = BindMaskName(newIndex); + + CVarSetString(cvarKey.c_str(), cvarName); + CVarSetInteger(maskKey.c_str(), 0); // default unbound + SetCount(count + 1); + + // Persist immediately (matches your current behavior) + CVarSave(); + }; + + auto RemoveBind = [&](int index) { + const int count = GetCount(); + if (index < 0 || index >= count) { + return; + } + + // Shift down [index+1..count-1] to [index..count-2] + for (int i = index; i < count - 1; i++) { + const std::string fromC = BindCVarName(i + 1); + const std::string toC = BindCVarName(i); + CVarCopy(fromC.c_str(), toC.c_str()); + + const std::string fromM = BindMaskName(i + 1); + const std::string toM = BindMaskName(i); + CVarCopy(fromM.c_str(), toM.c_str()); + } + + // Clear last slot (keeps registry tidy) + const std::string lastC = BindCVarName(count - 1); + const std::string lastM = BindMaskName(count - 1); + CVarClear(lastC.c_str()); + CVarClear(lastM.c_str()); + + SetCount(count - 1); + + // Persist immediately + CVarSave(); + }; + // --------------------------------------------------------------------- + // Top controls: Search (full width) + // --------------------------------------------------------------------- + PushStyleInput(THEME_COLOR); + + ImGui::AlignTextToFramePadding(); + ImGui::TextUnformatted("Search:"); + ImGui::SameLine(); + ImGui::SetNextItemWidth(-FLT_MIN); + sFilter.Draw("##CVarBindSearch"); + ImGui::Spacing(); + + // --------------------------------------------------------------------- + // Two panels that fill the remaining space (mod_menu-style sizing) + // --------------------------------------------------------------------- + if (ImGui::BeginTable("##CVarBindsTable", 2, + ImGuiTableFlags_BordersH | ImGuiTableFlags_BordersV | ImGuiTableFlags_Resizable | + ImGuiTableFlags_SizingStretchProp)) { + ImGui::TableSetupColumn("Matches", ImGuiTableColumnFlags_WidthStretch, 0.55f); + ImGui::TableSetupColumn("Binds", ImGuiTableColumnFlags_WidthStretch, 0.45f); + + // Header row, but disable interaction like your mod menu does + ImGui::PushItemFlag(ImGuiItemFlags_Disabled, true); + ImGui::TableHeadersRow(); + ImGui::PopItemFlag(); + + ImGui::TableNextRow(); + + // ========================= + // LEFT: Matches + // ========================= + ImGui::TableNextColumn(); + if (ImGui::BeginChild("##CVarBindMatchesPanel", ImVec2(0.0f, -8.0f), true)) { + // Panel header (kept subtle; table header already labels) + ImGui::TextDisabled("Click a CVar to add it"); + ImGui::Separator(); + + // Fill remaining height; -8 keeps bottom padding consistent + if (ImGui::BeginChild("##CVarBindMatchesList", ImVec2(0.0f, -8.0f), false)) { + for (const auto& e : all) { + if (!sFilter.PassFilter(e.label.c_str()) && !sFilter.PassFilter(e.name.c_str())) { + continue; + } + + std::string display = e.label; + if (display.empty()) { + display = e.name; + } else if (display != e.name) { + display += " ("; + display += e.name; + display += ")"; + } + + if (ImGui::Selectable(display.c_str(), false)) { + AddBind(e.name.c_str()); + } + + if (ImGui::IsItemHovered(ImGuiHoveredFlags_DelayShort)) { + ImGui::BeginTooltip(); + ImGui::TextUnformatted(e.name.c_str()); + ImGui::EndTooltip(); + } + } + } + ImGui::EndChild(); + + ImGui::EndChild(); + } + + // ========================= + // RIGHT: Binds + // ========================= + ImGui::TableNextColumn(); + if (ImGui::BeginChild("##CVarBindBindsPanel", ImVec2(0.0f, -8.0f), true)) { + ImGui::TextDisabled("Set a button combo to toggle the CVar"); + ImGui::Separator(); + + if (ImGui::BeginChild("##CVarBindBindsList", ImVec2(0.0f, -8.0f), false)) { + const int count = GetCount(); + + if (count == 0) { + ImGui::TextUnformatted("No binds yet."); + ImGui::TextUnformatted("Pick one from the Matches list."); + } + + for (int i = 0; i < count; i++) { + ImGui::PushID(i); + + const std::string cvarKey = BindCVarName(i); + const std::string maskKey = BindMaskName(i); + + const char* cvarName = CVarGetString(cvarKey.c_str(), ""); + if (cvarName == nullptr) { + cvarName = ""; + } + + std::string label = cvarName; + if (auto it = sNameToLabel.find(cvarName); it != sNameToLabel.end() && !it->second.empty()) { + label = it->second; + } + + // Row: label + remove button aligned right + ImGui::AlignTextToFramePadding(); + ImGui::TextUnformatted(label.c_str()); + ImGui::SameLine(); + + const float avail = ImGui::GetContentRegionAvail().x; + const float btnW = ImGui::CalcTextSize("Remove").x + (ImGui::GetStyle().FramePadding.x * 2.0f); + if (avail > btnW) { + ImGui::SetCursorPosX(ImGui::GetCursorPosX() + (avail - btnW)); + } + + if (ImGui::SmallButton("Remove")) { + RemoveBind(i); + ImGui::PopID(); + break; + } + + UIWidgets::BtnSelectorOptions btnOptions; + btnOptions.DefaultValue(0); + btnOptions.LabelPosition(UIWidgets::LabelPositions::Within); // <-- THIS is the key + + ImGui::SetNextItemWidth(-FLT_MIN); + if (UIWidgets::CVarBtnSelector("Button Combo:", maskKey.c_str(), btnOptions)) { + CVarSave(); + } + + ImGui::Separator(); + ImGui::PopID(); + } + } + ImGui::EndChild(); + + ImGui::EndChild(); + } + + ImGui::EndTable(); + } + PopStyleInput(); + }); +} } // namespace SohGui diff --git a/soh/soh/SohGui/UIWidgets.cpp b/soh/soh/SohGui/UIWidgets.cpp index ca5b4be9b86..c2aa143d53f 100644 --- a/soh/soh/SohGui/UIWidgets.cpp +++ b/soh/soh/SohGui/UIWidgets.cpp @@ -9,6 +9,7 @@ #include #include #include +#include "soh/OTRGlobals.h" namespace UIWidgets { @@ -1151,6 +1152,110 @@ void DrawFlagArray8Mask(const std::string& name, uint8_t& flags, Colors color) { } ImGui::PopID(); } + +std::map buttonMap = { + { "A", BTN_A }, + { "B", BTN_B }, + { "Z", BTN_Z }, + { "START", BTN_START }, + { "D-Up", BTN_DUP }, + { "D-Down", BTN_DDOWN }, + { "D-Left", BTN_DLEFT }, + { "D-Right", BTN_DRIGHT }, + { "L", BTN_L }, + { "R", BTN_R }, + { "C-Up", BTN_CUP }, + { "C-Down", BTN_CDOWN }, + { "C-Left", BTN_CLEFT }, + { "C-Right", BTN_CRIGHT }, + { "Modifier 1", BTN_CUSTOM_MODIFIER1 }, + { "Modifier 2", BTN_CUSTOM_MODIFIER2 }, +}; + +bool BtnSelector(const char* label, int32_t* value, const BtnSelectorOptions& options) { + bool dirty = false; + ImGui::PushID(label); + ImGui::BeginGroup(); + ImGui::AlignTextToFramePadding(); + ImGui::Text("%s", label); + ImGui::BeginDisabled(false); + PushStyleCombobox(options.color); + ImGui::BeginChild("ButtonCombo", ImVec2(0, ImGui::GetFrameHeightWithSpacing() + 14.0f), ImGuiChildFlags_None, + ImGuiWindowFlags_HorizontalScrollbar); + int32_t currentValue = *value; + int index = 0; + for (const auto& [buttonName, buttonMask] : buttonMap) { + if (currentValue & buttonMask) { + ImGui::PushID(buttonName.c_str()); + if (index++ > 0) { + ImGui::Text("+"); + ImGui::SameLine(); + } + if (UIWidgets::Button(buttonName.c_str(), UIWidgets::ButtonOptions() + .Tooltip("Remove this button from the combination") + .Color(UIWidgets::Colors::Gray) + .Size(UIWidgets::Sizes::Inline))) { + currentValue &= ~buttonMask; + dirty = true; + } + ImGui::PopID(); + ImGui::SameLine(); + } + } + if (UIWidgets::Button("+", UIWidgets::ButtonOptions({ { .tooltip = "Add a button to the combination" } }) + .Size(UIWidgets::Sizes::Inline) + .Color(options.color))) { + ImGui::OpenPopup("Add Button"); + } + if (ImGui::BeginPopup("Add Button")) { + UIWidgets::PushStyleMenuItem(); + for (const auto& [buttonName, buttonMask] : buttonMap) { + if (!(currentValue & buttonMask)) { + if (ImGui::MenuItem(buttonName.c_str())) { + currentValue |= buttonMask; + dirty = true; + } + } + } + UIWidgets::PopStyleMenuItem(); + ImGui::EndPopup(); + } + ImGui::SameLine(); + if (UIWidgets::Button(ICON_FA_UNDO, + UIWidgets::ButtonOptions().Size(UIWidgets::Sizes::Inline).Color(options.color))) { + currentValue = options.defaultValue; + dirty = true; + } + ImGui::EndChild(); + PopStyleCombobox(); + ImGui::EndDisabled(); + ImGui::EndGroup(); + ImGui::PopID(); + if (dirty) { + *value = currentValue; + } + return dirty; +} + +bool CVarBtnSelector(const char* label, const char* cvarName, const BtnSelectorOptions& options) { + bool dirty = false; + + int32_t value = CVarGetInteger(cvarName, options.defaultValue); + + // If the cvar system uses -1 as "unset", show the real defaults instead of "all buttons". + if (value == -1) { + value = options.defaultValue; + } + + if (BtnSelector(label, &value, options)) { + CVarSetInteger(cvarName, value); + Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); + ShipInit::Init(cvarName); + dirty = true; + } + + return dirty; +} } // namespace UIWidgets ImVec4 GetRandomValue() { diff --git a/soh/soh/SohGui/UIWidgets.hpp b/soh/soh/SohGui/UIWidgets.hpp index 95590b1ce5c..24a26c9cc0e 100644 --- a/soh/soh/SohGui/UIWidgets.hpp +++ b/soh/soh/SohGui/UIWidgets.hpp @@ -110,6 +110,12 @@ struct WidgetOptions { const char* tooltip = ""; bool disabled = false; const char* disabledTooltip = ""; + Colors color = Colors::NoColor; + + WidgetOptions& Color(Colors color_) { + color = color = color_; + return *this; + } WidgetOptions& Tooltip(const char* tooltip_) { tooltip = tooltip_; @@ -499,6 +505,38 @@ struct FloatSliderOptions : WidgetOptions { } }; +struct BtnSelectorOptions : WidgetOptions { + s32 defaultValue = 0; + ComponentAlignments alignment = ComponentAlignments::Left; + LabelPositions labelPosition = LabelPositions::Above; + Colors color = Colors::Gray; + + BtnSelectorOptions& DefaultValue(int32_t defaultValue_) { + defaultValue = defaultValue_; + return *this; + } + + BtnSelectorOptions& ComponentAlignment(ComponentAlignments alignment_) { + alignment = alignment_; + return *this; + } + + BtnSelectorOptions& LabelPosition(LabelPositions labelPosition_) { + labelPosition = labelPosition_; + return *this; + } + + BtnSelectorOptions& Tooltip(const char* tooltip_) { + WidgetOptions::tooltip = tooltip_; + return *this; + } + + BtnSelectorOptions& Color(Colors color_) { + WidgetOptions::color = color = color_; + return *this; + } +}; + struct RadioButtonsOptions : WidgetOptions { std::unordered_map buttonMap; int32_t defaultIndex = 0; @@ -1043,6 +1081,8 @@ void DrawFlagArray32(const std::string& name, uint32_t& flags, Colors color = Co void DrawFlagArray16(const std::string& name, uint16_t& flags, Colors color = Colors::LightBlue); void DrawFlagArray8(const std::string& name, uint8_t& flags, Colors color = Colors::LightBlue); void DrawFlagArray8Mask(const std::string& name, uint8_t& flags, Colors color = Colors::LightBlue); +bool BtnSelector(const char* label, int32_t* value, const BtnSelectorOptions& options); +bool CVarBtnSelector(const char* label, const char* cvarName, const BtnSelectorOptions& options); void InsertHelpHoverText(const std::string& text); void InsertHelpHoverText(const char* text); diff --git a/soh/soh/cvar_prefixes.h b/soh/soh/cvar_prefixes.h index 1ac1ea7b9a3..0ae5fb4cc09 100644 --- a/soh/soh/cvar_prefixes.h +++ b/soh/soh/cvar_prefixes.h @@ -15,6 +15,7 @@ #define CVAR_REMOTE(var) CVAR_PREFIX_REMOTE "." var #define CVAR_REMOTE_CROWD_CONTROL(var) CVAR_REMOTE("CrowdControl." var) #define CVAR_REMOTE_SAIL(var) CVAR_REMOTE("Sail." var) +#define CVAR_REMOTE_ARCHIPELAGO(var) CVAR_REMOTE("Archipelago." var) #define CVAR_REMOTE_ANCHOR(var) CVAR_REMOTE("Anchor." var) #define CVAR_GAMEPLAY_STATS(var) CVAR_PREFIX_GAMEPLAY_STATS "." var #define CVAR_TIME_DISPLAY(var) CVAR_PREFIX_TIME_DISPLAY "." var \ No newline at end of file diff --git a/soh/soh/util.cpp b/soh/soh/util.cpp index 159ccfbe2f2..d0d09685b62 100644 --- a/soh/soh/util.cpp +++ b/soh/soh/util.cpp @@ -774,6 +774,20 @@ size_t SohUtils::CopyStringToCharBuffer(char* buffer, const std::string& source, return 0; } +int SohUtils::CopyStringToCharBuffer(const std::string& inputStr, char* buffer, const int maxBufferSize) { + if (!inputStr.empty()) { + // Prevent potential horrible overflow due to implicit conversion of maxBufferSize to an unsigned. Prevents + // negatives. + memset(buffer, 0, std::max(0, maxBufferSize)); + // Gaurentee that this value will be greater than 0, regardless of passed variables. + const int copiedCharLen = std::min(std::max(0, maxBufferSize - 1), inputStr.length()); + memcpy(buffer, inputStr.c_str(), copiedCharLen); + return copiedCharLen; + } + + return 0; +} + bool SohUtils::IsStringEmpty(std::string str) { // Remove spaces at the beginning of the string std::string::size_type start = str.find_first_not_of(' '); diff --git a/soh/soh/util.h b/soh/soh/util.h index f704076b4d9..75bb737515a 100644 --- a/soh/soh/util.h +++ b/soh/soh/util.h @@ -22,6 +22,7 @@ std::string Sanitize(std::string stringValue); // Copies a string into a char buffer up to maxBufferSize characters. This does NOT insert a null terminator // on the end, as this is used for in-game messages which are not null-terminated. size_t CopyStringToCharBuffer(char* buffer, const std::string& source, size_t maxBufferSize); +int CopyStringToCharBuffer(const std::string& inputStr, char* buffer, const int maxBufferSize); bool IsStringEmpty(std::string str); uint32_t Hash(std::string str); diff --git a/soh/soh/z_scene_otr.cpp b/soh/soh/z_scene_otr.cpp index 1da9d729a5f..bdfee23da65 100644 --- a/soh/soh/z_scene_otr.cpp +++ b/soh/soh/z_scene_otr.cpp @@ -517,6 +517,7 @@ extern "C" s32 OTRfunc_8009728C(PlayState* play, RoomContext* roomCtx, s32 roomN roomCtx->unk_30 ^= 1; SPDLOG_INFO("Room Init - curRoom.num: {0:#x}", roomCtx->curRoom.num); + GameInteractor_ExecuteOnRoomInit(roomCtx->curRoom.num); return 1; } diff --git a/soh/src/code/graph.c b/soh/src/code/graph.c index 9694b7a4e17..06148d04eb4 100644 --- a/soh/src/code/graph.c +++ b/soh/src/code/graph.c @@ -408,9 +408,11 @@ void Graph_Update(GraphicsContext* gfxCtx, GameState* gameState) { sGraphUpdateTime = time; } + auto mask = CVarGetInteger("gDeveloperTools.MapSelectBtn", BTN_Z | BTN_L | BTN_R); + if (CVarGetInteger(CVAR_DEVELOPER_TOOLS("DebugEnabled"), 0)) { - if (CHECK_BTN_ALL(gameState->input[0].press.button, BTN_Z) && - CHECK_BTN_ALL(gameState->input[0].cur.button, BTN_L | BTN_R)) { + if (CHECK_BTN_ANY(gameState->input[0].press.button, mask) && + CHECK_BTN_ALL(gameState->input[0].cur.button, mask)) { gSaveContext.gameMode = GAMEMODE_NORMAL; SET_NEXT_GAMESTATE(gameState, Select_Init, SelectContext); gameState->running = false; diff --git a/soh/src/code/z_actor.c b/soh/src/code/z_actor.c index d1386a48a6b..b83edfc10ed 100644 --- a/soh/src/code/z_actor.c +++ b/soh/src/code/z_actor.c @@ -2796,6 +2796,7 @@ void Actor_Draw(PlayState* play, Actor* actor) { } actor->draw(actor, play); + GameInteractor_ExecuteOnActorDraw(actor); if (actor->colorFilterTimer != 0) { if (actor->colorFilterParams & 0x2000) { @@ -3351,7 +3352,8 @@ Actor* Actor_Spawn(ActorContext* actorCtx, PlayState* play, s16 actorId, f32 pos objBankIndex = Object_GetIndex(&gPlayState->objectCtx, dbEntry->objectId); - if (objBankIndex < 0 && (!gMapLoading || CVarGetInteger(CVAR_ENHANCEMENT("RandomizedEnemies"), 0))) { + if (objBankIndex < 0 && (!gMapLoading || CVarGetInteger(CVAR_ENHANCEMENT("RandomizedEnemies"), 0) || + CVarGetInteger("gHoliday.Gameplay.Blitz.Enabled", 0))) { objBankIndex = 0; } diff --git a/soh/src/code/z_collision_check.c b/soh/src/code/z_collision_check.c index aa466e412eb..af5e6585e48 100644 --- a/soh/src/code/z_collision_check.c +++ b/soh/src/code/z_collision_check.c @@ -2,6 +2,7 @@ #include "vt.h" #include "overlays/effects/ovl_Effect_Ss_HitMark/z_eff_ss_hitmark.h" #include "soh/Enhancements/game-interactor/GameInteractor.h" +#include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h" #include typedef s32 (*ColChkResetFunc)(PlayState*, Collider*); @@ -3030,6 +3031,13 @@ void CollisionCheck_ApplyDamage(PlayState* play, CollisionCheckContext* colChkCt if (CVarGetInteger(CVAR_ENHANCEMENT("IvanCoopModeEnabled"), 0)) { collider->actor->colChkInfo.damage *= GET_PLAYER(play)->ivanDamageMultiplier; } + + if (!GameInteractor_Should(VB_APPLY_DAMAGE_TO_ACTOR, true, collider->actor, + collider->actor->colChkInfo.damageEffect, collider->actor->colChkInfo.damage, + info->acHitInfo->toucher.dmgFlags)) { + collider->actor->colChkInfo.damageEffect = 0; + collider->actor->colChkInfo.damage = 0; + } } /** diff --git a/soh/src/code/z_draw.c b/soh/src/code/z_draw.c index b1b4c1cc5bb..67cca0267af 100644 --- a/soh/src/code/z_draw.c +++ b/soh/src/code/z_draw.c @@ -399,6 +399,10 @@ DrawItemTableEntry sDrawItemTable[] = { * Calls the corresponding draw function for the given draw ID */ void GetItem_Draw(PlayState* play, s16 drawId) { + // SoH [Enhancements] Prevent any UB here, GID_MAXIMUM useful for overriding GI draws + if (drawId < 0 || drawId >= GID_MAXIMUM) + return; + sDrawItemTable[drawId].drawFunc(play, drawId); } @@ -1065,4 +1069,4 @@ void GetItem_DrawFishingPole(PlayState* play, s16 drawId) { Matrix_Pop(); CLOSE_DISPS(play->state.gfxCtx); -} \ No newline at end of file +} diff --git a/soh/src/code/z_kankyo.c b/soh/src/code/z_kankyo.c index d78a93c6c72..ad47d19fd69 100644 --- a/soh/src/code/z_kankyo.c +++ b/soh/src/code/z_kankyo.c @@ -2324,6 +2324,31 @@ void Environment_DrawSandstorm(PlayState* play, u8 sandstormState) { Environment_PatchSandstorm(play); + if (CVarGetInteger("gHoliday.Visual.SnowingWeather", 0)) { + // Prim colors + sSandstormPrimColors[0] = (Color_RGB8){ 210, 210, 210 }; + sSandstormPrimColors[1] = (Color_RGB8){ 255, 255, 255 }; + sSandstormPrimColors[2] = (Color_RGB8){ 225, 225, 225 }; + sSandstormPrimColors[3] = (Color_RGB8){ 105, 105, 105 }; + + // Env colors + sSandstormEnvColors[0] = (Color_RGB8){ 155, 155, 155 }; + sSandstormEnvColors[1] = (Color_RGB8){ 200, 200, 200 }; + sSandstormEnvColors[2] = (Color_RGB8){ 170, 170, 170 }; + sSandstormEnvColors[3] = (Color_RGB8){ 50, 50, 50 }; + } else { + // Restore vanilla colors when the CVAR is off + sSandstormPrimColors[0] = (Color_RGB8){ 210, 156, 85 }; + sSandstormPrimColors[1] = (Color_RGB8){ 255, 200, 100 }; + sSandstormPrimColors[2] = (Color_RGB8){ 225, 160, 50 }; + sSandstormPrimColors[3] = (Color_RGB8){ 105, 90, 40 }; + + sSandstormEnvColors[0] = (Color_RGB8){ 155, 106, 35 }; + sSandstormEnvColors[1] = (Color_RGB8){ 200, 150, 50 }; + sSandstormEnvColors[2] = (Color_RGB8){ 170, 110, 0 }; + sSandstormEnvColors[3] = (Color_RGB8){ 50, 40, 0 }; + } + switch (sandstormState) { case SANDSTORM_ACTIVE: if ((play->sceneNum == SCENE_HAUNTED_WASTELAND) && (play->roomCtx.curRoom.num == 0)) { diff --git a/soh/src/code/z_message_PAL.c b/soh/src/code/z_message_PAL.c index 25efbaadd1c..e698dd09aa4 100644 --- a/soh/src/code/z_message_PAL.c +++ b/soh/src/code/z_message_PAL.c @@ -2713,6 +2713,9 @@ void Message_OpenText(PlayState* play, u16 textId) { Font* font = &msgCtx->font; s16 textBoxType; + bool loadFromMessageTable = true; + GameInteractor_ExecuteOnOpenText(&textId, &loadFromMessageTable); + sDisplayNextMessageAsEnglish = false; if (msgCtx->msgMode == MSGMODE_NONE) { @@ -2782,7 +2785,9 @@ void Message_OpenText(PlayState* play, u16 textId) { gSaveContext.eventInf[0] = gSaveContext.eventInf[1] = gSaveContext.eventInf[2] = gSaveContext.eventInf[3] = 0; } - if (CustomMessage_RetrieveIfExists(play)) { + if (!loadFromMessageTable) { + // no-op + } else if (CustomMessage_RetrieveIfExists(play)) { osSyncPrintf("Found custom message"); if (gSaveContext.language == LANGUAGE_JPN) { sDisplayNextMessageAsEnglish = true; diff --git a/soh/src/code/z_parameter.c b/soh/src/code/z_parameter.c index 3b1d91f056a..33cf86bdbc7 100644 --- a/soh/src/code/z_parameter.c +++ b/soh/src/code/z_parameter.c @@ -11,6 +11,7 @@ #include "soh/Enhancements/custom-message/CustomMessageInterfaceAddon.h" #include "soh/Enhancements/cosmetics/cosmeticsTypes.h" #include "soh/Enhancements/enhancementTypes.h" +#include "soh/Enhancements/FileSelectEnhancements.h" #include "soh/ShipUtils.h" #include @@ -1872,6 +1873,10 @@ u8 Return_Item(u8 itemID, ModIndex modId, ItemID returnItem) { * @return u8 */ u8 Item_Give(PlayState* play, u8 item) { + // SoH [Enhancements] Ignore ITEM_SHIP, used for CustomItem + if (item == ITEM_SHIP) + return ITEM_NONE; + // prevents getting sticks without the bag in case something got missed if (IS_RANDO && (item == ITEM_STICK || item == ITEM_STICKS_5 || item == ITEM_STICKS_10) && Randomizer_GetSettingValue(RSK_SHUFFLE_DEKU_STICK_BAG) && CUR_UPG_VALUE(UPG_STICKS) == 0) { @@ -2476,6 +2481,10 @@ u8 Item_CheckObtainability(u8 item) { s16 slot = SLOT(item); s32 temp; + // SoH [Enhancements] Ignore ITEM_SHIP, used for CustomItem + if (item == ITEM_SHIP) + return ITEM_NONE; + if (item >= ITEM_STICKS_5) { slot = SLOT(sExtraItemBases[item - ITEM_STICKS_5]); } @@ -3426,6 +3435,52 @@ void Interface_DrawLineupTick(PlayState* play) { CLOSE_DISPS(play->state.gfxCtx); } +void Interface_DrawArchipelagoStatusString(PlayState* play) { + InterfaceContext* interfaceCtx = &play->interfaceCtx; + OPEN_DISPS(play->state.gfxCtx); + + Gfx_SetupDL_39Overlay(play->state.gfxCtx); + + s16 posX = OTRGetRectDimensionFromLeftEdge(4); + s16 posY = SCREEN_HEIGHT - 12; + + int32_t scale = R_TEXT_CHAR_SCALE * 0.2f; + int32_t sTexSize = (scale / 100.0f) * 64.0f; + int32_t sTexScale = 1024.0f / (scale / 100.0f); + + gDPSetEnvColor(OVERLAY_DISP++, 255, 255, 255, 255); + gDPSetPrimColor(OVERLAY_DISP++, 0, 0, 255, 255, 255, 255); + + gDPLoadTextureBlock(OVERLAY_DISP++, gArchipelagoItemTex, G_IM_FMT_RGBA, G_IM_SIZ_32b, 64, 64, 0, + G_TX_NOMIRROR | G_TX_WRAP, G_TX_NOMIRROR | G_TX_WRAP, 0, 0, G_TX_NOLOD, G_TX_NOLOD); + + gSPWideTextureRectangle(OVERLAY_DISP++, posX << 2, posY << 2, (posX + sTexSize) << 2, (posY + sTexSize) << 2, + G_TX_RENDERTILE, 0, 0, sTexScale, sTexScale); + + posX += 13; + + uint8_t language = (gSaveContext.language == LANGUAGE_JPN) ? LANGUAGE_ENG : gSaveContext.language; + char* statusText = SohFileSelect_GetArchipelagoSettingText(ASM_NOT_CONNECTED, language); + switch (CVarGetInteger(CVAR_REMOTE_ARCHIPELAGO("ConnectionStatus"), 0)) { + case 0: // Not Connected + statusText = SohFileSelect_GetArchipelagoSettingText(ASM_NOT_CONNECTED, language); + break; + case 1: // Connecting + case 2: // Connection error, retrying + case 3: // Connected + statusText = SohFileSelect_GetArchipelagoSettingText(ASM_CONNECTING, language); + break; + case 4: // Connected + Locations Scouted + statusText = SohFileSelect_GetArchipelagoSettingText(ASM_CONNECTED, language); + break; + } + + Interface_DrawTextLineOverlay(play->state.gfxCtx, statusText, posX, posY, 255, 255, 255, 255, 0.8f, true); + + gDPPipeSync(OVERLAY_DISP++); + CLOSE_DISPS(play->state.gfxCtx); +} + void Interface_DrawMagicBar(PlayState* play) { InterfaceContext* interfaceCtx = &play->interfaceCtx; s16 magicDrop = R_MAGIC_BAR_LARGE_Y - R_MAGIC_BAR_SMALL_Y + 2; @@ -3441,8 +3496,8 @@ void Interface_DrawMagicBar(PlayState* play) { if (CVarGetInteger(CVAR_COSMETIC("Consumable.Magic.Changed"), 0)) { magicbar_green = CVarGetColor24(CVAR_COSMETIC("Consumable.Magic.Value"), magicbar_green); } - if (CVarGetInteger("gCosmetics.Consumable_MagicInfinite.Changed", 0)) { - magicbar_blue = CVarGetColor24("gCosmetics.Consumable_MagicInfinite.Value", magicbar_blue); + if (CVarGetInteger(CVAR_COSMETIC("Consumable.MagicInfinite.Changed"), 0)) { + magicbar_blue = CVarGetColor24(CVAR_COSMETIC("Consumable.MagicInfinite.Value"), magicbar_blue); } OPEN_DISPS(play->state.gfxCtx); @@ -4897,6 +4952,11 @@ void Interface_DrawAmmoCount(PlayState* play, s16 button, s16 alpha) { } ammo = AMMO(i); + if (CVarGetInteger("gHoliday.Gameplay.BombArrows.Active", 0) && + gSaveContext.equips.buttonItems[button] == ITEM_BOW && AMMO(ITEM_BOMB) != 0 && + AMMO(ITEM_BOMB) < AMMO(ITEM_BOW)) { + ammo = AMMO(ITEM_BOMB); + } gDPPipeSync(OVERLAY_DISP++); @@ -4909,6 +4969,11 @@ void Interface_DrawAmmoCount(PlayState* play, s16 button, s16 alpha) { if (ammo < 0) { ammo = 0; } + } else if (gSaveContext.equips.buttonItems[button] == ITEM_BOW && + CVarGetInteger("gHoliday.Gameplay.BombArrows.Active", 0)) { + if (AMMO(ITEM_BOMB) != 0 && ammo == MIN(CUR_CAPACITY(UPG_QUIVER), CUR_CAPACITY(UPG_BOMB_BAG))) { + gDPSetPrimColor(OVERLAY_DISP++, 0, 0, 120, 255, 0, alpha); + } } else if (((i == ITEM_BOW) && (AMMO(i) == CUR_CAPACITY(UPG_QUIVER))) || ((i == ITEM_BOMB) && (AMMO(i) == CUR_CAPACITY(UPG_BOMB_BAG))) || ((i == ITEM_SLINGSHOT) && (AMMO(i) == CUR_CAPACITY(UPG_BULLET_BAG))) || @@ -5399,6 +5464,10 @@ void Interface_Draw(PlayState* play) { Interface_DrawLineupTick(play); } + if (IS_ARCHIPELAGO) { + Interface_DrawArchipelagoStatusString(play); + } + if (fullUi || gSaveContext.magicState > MAGIC_STATE_IDLE) { Interface_DrawMagicBar(play); } @@ -5524,6 +5593,10 @@ void Interface_Draw(PlayState* play) { if (gSaveContext.equips.buttonItems[1] < 0xF0) { gDPSetPrimColor(OVERLAY_DISP++, 0, 0, 255, 255, 255, interfaceCtx->cLeftAlpha); gDPSetCombineMode(OVERLAY_DISP++, G_CC_MODULATERGBA_PRIM, G_CC_MODULATERGBA_PRIM); + if (gSaveContext.equips.buttonItems[1] == ITEM_BOW && + CVarGetInteger("gHoliday.Gameplay.BombArrows.Active", 0)) { + Interface_DrawItemIconTexture(play, gItemIcons[ITEM_BOMB], 1); + } Interface_DrawItemIconTexture(play, gItemIcons[gSaveContext.equips.buttonItems[1]], 1); gDPPipeSync(OVERLAY_DISP++); gDPSetCombineLERP(OVERLAY_DISP++, PRIMITIVE, ENVIRONMENT, TEXEL0, ENVIRONMENT, TEXEL0, 0, PRIMITIVE, 0, @@ -5537,6 +5610,10 @@ void Interface_Draw(PlayState* play) { if (gSaveContext.equips.buttonItems[2] < 0xF0) { gDPSetPrimColor(OVERLAY_DISP++, 0, 0, 255, 255, 255, interfaceCtx->cDownAlpha); gDPSetCombineMode(OVERLAY_DISP++, G_CC_MODULATERGBA_PRIM, G_CC_MODULATERGBA_PRIM); + if (gSaveContext.equips.buttonItems[2] == ITEM_BOW && + CVarGetInteger("gHoliday.Gameplay.BombArrows.Active", 0)) { + Interface_DrawItemIconTexture(play, gItemIcons[ITEM_BOMB], 2); + } Interface_DrawItemIconTexture(play, gItemIcons[gSaveContext.equips.buttonItems[2]], 2); gDPPipeSync(OVERLAY_DISP++); gDPSetCombineLERP(OVERLAY_DISP++, PRIMITIVE, ENVIRONMENT, TEXEL0, ENVIRONMENT, TEXEL0, 0, PRIMITIVE, 0, @@ -5550,6 +5627,10 @@ void Interface_Draw(PlayState* play) { if (gSaveContext.equips.buttonItems[3] < 0xF0) { gDPSetPrimColor(OVERLAY_DISP++, 0, 0, 255, 255, 255, interfaceCtx->cRightAlpha); gDPSetCombineMode(OVERLAY_DISP++, G_CC_MODULATERGBA_PRIM, G_CC_MODULATERGBA_PRIM); + if (gSaveContext.equips.buttonItems[3] == ITEM_BOW && + CVarGetInteger("gHoliday.Gameplay.BombArrows.Active", 0)) { + Interface_DrawItemIconTexture(play, gItemIcons[ITEM_BOMB], 3); + } Interface_DrawItemIconTexture(play, gItemIcons[gSaveContext.equips.buttonItems[3]], 3); gDPPipeSync(OVERLAY_DISP++); gDPSetCombineLERP(OVERLAY_DISP++, PRIMITIVE, ENVIRONMENT, TEXEL0, ENVIRONMENT, TEXEL0, 0, PRIMITIVE, 0, @@ -5616,6 +5697,10 @@ void Interface_Draw(PlayState* play) { if (gSaveContext.equips.buttonItems[4] < 0xF0) { gDPSetPrimColor(OVERLAY_DISP++, 0, 0, 255, 255, 255, interfaceCtx->dpadUpAlpha); gDPSetCombineMode(OVERLAY_DISP++, G_CC_MODULATERGBA_PRIM, G_CC_MODULATERGBA_PRIM); + if (gSaveContext.equips.buttonItems[4] == ITEM_BOW && + CVarGetInteger("gHoliday.Gameplay.BombArrows.Active", 0)) { + Interface_DrawItemIconTexture(play, gItemIcons[ITEM_BOMB], 4); + } Interface_DrawItemIconTexture(play, gItemIcons[gSaveContext.equips.buttonItems[4]], 4); gDPPipeSync(OVERLAY_DISP++); gDPSetCombineLERP(OVERLAY_DISP++, PRIMITIVE, ENVIRONMENT, TEXEL0, ENVIRONMENT, TEXEL0, 0, PRIMITIVE, 0, @@ -5627,6 +5712,10 @@ void Interface_Draw(PlayState* play) { if (gSaveContext.equips.buttonItems[5] < 0xF0) { gDPSetPrimColor(OVERLAY_DISP++, 0, 0, 255, 255, 255, interfaceCtx->dpadDownAlpha); gDPSetCombineMode(OVERLAY_DISP++, G_CC_MODULATERGBA_PRIM, G_CC_MODULATERGBA_PRIM); + if (gSaveContext.equips.buttonItems[5] == ITEM_BOW && + CVarGetInteger("gHoliday.Gameplay.BombArrows.Active", 0)) { + Interface_DrawItemIconTexture(play, gItemIcons[ITEM_BOMB], 5); + } Interface_DrawItemIconTexture(play, gItemIcons[gSaveContext.equips.buttonItems[5]], 5); gDPPipeSync(OVERLAY_DISP++); gDPSetCombineLERP(OVERLAY_DISP++, PRIMITIVE, ENVIRONMENT, TEXEL0, ENVIRONMENT, TEXEL0, 0, PRIMITIVE, 0, @@ -5638,6 +5727,10 @@ void Interface_Draw(PlayState* play) { if (gSaveContext.equips.buttonItems[6] < 0xF0) { gDPSetPrimColor(OVERLAY_DISP++, 0, 0, 255, 255, 255, interfaceCtx->dpadLeftAlpha); gDPSetCombineMode(OVERLAY_DISP++, G_CC_MODULATERGBA_PRIM, G_CC_MODULATERGBA_PRIM); + if (gSaveContext.equips.buttonItems[6] == ITEM_BOW && + CVarGetInteger("gHoliday.Gameplay.BombArrows.Active", 0)) { + Interface_DrawItemIconTexture(play, gItemIcons[ITEM_BOMB], 6); + } Interface_DrawItemIconTexture(play, gItemIcons[gSaveContext.equips.buttonItems[6]], 6); gDPPipeSync(OVERLAY_DISP++); gDPSetCombineLERP(OVERLAY_DISP++, PRIMITIVE, ENVIRONMENT, TEXEL0, ENVIRONMENT, TEXEL0, 0, PRIMITIVE, 0, @@ -5649,6 +5742,10 @@ void Interface_Draw(PlayState* play) { if (gSaveContext.equips.buttonItems[7] < 0xF0) { gDPSetPrimColor(OVERLAY_DISP++, 0, 0, 255, 255, 255, interfaceCtx->dpadRightAlpha); gDPSetCombineMode(OVERLAY_DISP++, G_CC_MODULATERGBA_PRIM, G_CC_MODULATERGBA_PRIM); + if (gSaveContext.equips.buttonItems[7] == ITEM_BOW && + CVarGetInteger("gHoliday.Gameplay.BombArrows.Active", 0)) { + Interface_DrawItemIconTexture(play, gItemIcons[ITEM_BOMB], 7); + } Interface_DrawItemIconTexture(play, gItemIcons[gSaveContext.equips.buttonItems[7]], 7); gDPPipeSync(OVERLAY_DISP++); gDPSetCombineLERP(OVERLAY_DISP++, PRIMITIVE, ENVIRONMENT, TEXEL0, ENVIRONMENT, TEXEL0, 0, PRIMITIVE, 0, @@ -6937,6 +7034,41 @@ void Interface_DrawTextCharacter(GraphicsContext* gfx, int16_t x, int16_t y, voi CLOSE_DISPS(gfx); } +void Interface_DrawTextCharacter_overlay(GraphicsContext* gfx, int16_t x, int16_t y, void* texture, uint16_t colorR, + uint16_t colorG, uint16_t colorB, uint16_t colorA, float textScale, + uint8_t textShadow) { + + int32_t scale = R_TEXT_CHAR_SCALE * textScale; + int32_t sCharTexSize = (scale / 100.0f) * 16.0f; + int32_t sCharTexScale = 1024.0f / (scale / 100.0f); + + OPEN_DISPS(gfx); + + gDPPipeSync(OVERLAY_DISP++); + + gDPLoadTextureBlock_4b(OVERLAY_DISP++, texture, G_IM_FMT_I, FONT_CHAR_TEX_WIDTH, FONT_CHAR_TEX_HEIGHT, 0, + G_TX_NOMIRROR | G_TX_CLAMP, G_TX_NOMIRROR | G_TX_CLAMP, G_TX_NOMASK, G_TX_NOMASK, G_TX_NOLOD, + G_TX_NOLOD); + + if (textShadow) { + // Draw drop shadow + gDPSetPrimColor(OVERLAY_DISP++, 0, 0, 0, 0, 0, colorA); + gSPWideTextureRectangle( + OVERLAY_DISP++, (x + R_TEXT_DROP_SHADOW_OFFSET) << 2, (y + R_TEXT_DROP_SHADOW_OFFSET) << 2, + (x + R_TEXT_DROP_SHADOW_OFFSET + sCharTexSize) << 2, (y + R_TEXT_DROP_SHADOW_OFFSET + sCharTexSize) << 2, + G_TX_RENDERTILE, 0, 0, sCharTexScale, sCharTexScale); + } + + gDPPipeSync(OVERLAY_DISP++); + + // Draw normal text + gDPSetPrimColor(OVERLAY_DISP++, 0, 0, colorR, colorG, colorB, colorA); + gSPWideTextureRectangle(OVERLAY_DISP++, x << 2, y << 2, (x + sCharTexSize) << 2, (y + sCharTexSize) << 2, + G_TX_RENDERTILE, 0, 0, sCharTexScale, sCharTexScale); + + CLOSE_DISPS(gfx); +} + uint16_t Interface_DrawTextLine(GraphicsContext* gfx, char text[], int16_t x, int16_t y, uint16_t colorR, uint16_t colorG, uint16_t colorB, uint16_t colorA, float textScale, uint8_t textShadow) { @@ -6967,3 +7099,34 @@ uint16_t Interface_DrawTextLine(GraphicsContext* gfx, char text[], int16_t x, in return kerningOffset; } + +uint16_t Interface_DrawTextLineOverlay(GraphicsContext* gfx, char text[], int16_t x, int16_t y, uint16_t colorR, + uint16_t colorG, uint16_t colorB, uint16_t colorA, float textScale, + uint8_t textShadow) { + + uint16_t textureIndex; + uint16_t kerningOffset = 0; + uint16_t lineOffset = 0; + void* texture; + const char* processedText = Interface_ReplaceSpecialCharacters(text); + uint8_t textLength = strlen(processedText); + + for (uint16_t i = 0; i < textLength; i++) { + if (processedText[i] == '\n') { + lineOffset += 15 * textScale; + kerningOffset = 0; + } else { + textureIndex = processedText[i] - 32; + + if (textureIndex != 0) { + texture = Ship_GetCharFontTexture(processedText[i]); + Interface_DrawTextCharacter_overlay(gfx, (int16_t)(x + kerningOffset), (int16_t)(y + lineOffset), + texture, colorR, colorG, colorB, colorA, textScale, textShadow); + } + kerningOffset += + (uint16_t)(Ship_GetCharFontWidth(processedText[i]) * (R_TEXT_CHAR_SCALE / 100.0f) * textScale); + } + } + + return kerningOffset; +} diff --git a/soh/src/code/z_play.c b/soh/src/code/z_play.c index 89583e5984f..b85269331e5 100644 --- a/soh/src/code/z_play.c +++ b/soh/src/code/z_play.c @@ -38,6 +38,9 @@ Input* D_8012D1F8 = NULL; PlayState* gPlayState; s16 firstInit = 0; s16 gEnPartnerId; +s16 gEnSnowballId; +s16 gEnChristmasTreeId; +s16 gEnChristmasDecoId; void Play_SpawnScene(PlayState* play, s32 sceneId, s32 spawn); @@ -687,6 +690,14 @@ void Play_Init(GameState* thisx) { GET_PLAYER(play)->actor.world.pos.y + Player_GetHeight(GET_PLAYER(play)) + 5.0f, GET_PLAYER(play)->actor.world.pos.z, 0, 0, 0, 1, true); } + + if (play->sceneNum == SCENE_KAKARIKO_VILLAGE && CVarGetInteger(CVAR_GENERAL("LetItSnow"), 0)) { + Actor_Spawn(&play->actorCtx, play, gEnChristmasTreeId, -545, 0, 404, 0, 0, 0, 0, true); + } + + Actor_Spawn(&play->actorCtx, play, gEnChristmasDecoId, 0, 0, 0, 0, 0, 0, 0, true); + // nextEntranceIndex was not initialized, so the previous value was carried over during soft resets. + gPlayState->nextEntranceIndex = gSaveContext.entranceIndex; } void Play_Update(PlayState* play) { @@ -1596,6 +1607,10 @@ void Play_Draw(PlayState* play) { if ((HREG(80) != 10) || (HREG(88) != 0)) { if (play->envCtx.sandstormState != SANDSTORM_OFF) { Environment_DrawSandstorm(play, play->envCtx.sandstormState); + } else if (CVarGetInteger("gHoliday.Visual.SnowingWeatherActive", 0) == 1) { + Environment_DrawSandstorm(play, SANDSTORM_DISSIPATE); + } else if (CVarGetInteger("gHoliday.Visual.SnowingWeatherActive", 0) == 2) { + Environment_DrawSandstorm(play, SANDSTORM_ACTIVE); } } @@ -1692,7 +1707,7 @@ time_t Play_GetRealTime() { void Play_Main(GameState* thisx) { PlayState* play = (PlayState*)thisx; - if (play->envCtx.unk_EE[2] == 0 && CVarGetInteger(CVAR_GENERAL("LetItSnow"), 0)) { + if (play->envCtx.unk_EE[2] == 0 && CVarGetInteger("gHoliday.Visual.SnowingWeather", 0)) { play->envCtx.unk_EE[3] = 64; Actor_Spawn(&gPlayState->actorCtx, gPlayState, ACTOR_OBJECT_KANKYO, 0, 0, 0, 0, 0, 0, 3, 0); } diff --git a/soh/src/code/z_player_lib.c b/soh/src/code/z_player_lib.c index 49d9380bf66..714d4c820ba 100644 --- a/soh/src/code/z_player_lib.c +++ b/soh/src/code/z_player_lib.c @@ -9,9 +9,12 @@ #include "soh/Enhancements/game-interactor/GameInteractor.h" #include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h" #include "soh/Enhancements/randomizer/draw.h" +#include "soh/Enhancements/Holiday/Fredomato.h" #include "soh/ResourceManagerHelpers.h" #include +#include "soh_assets.h" +#include "soh/Enhancements/Holiday/Archez.h" typedef struct { /* 0x00 */ u8 flag; @@ -1015,12 +1018,20 @@ void* sMouthTextures[] = { }; #endif +// Original colors Color_RGB8 sTunicColors[] = { { 30, 105, 27 }, { 100, 20, 0 }, { 0, 60, 100 }, }; +// Overwrite to red tunic as default for Holidays in Hyrule build +// Color_RGB8 sTunicColors[] = { +// { 255, 0, 0 }, +// { 255, 0, 0 }, +// { 255, 0, 0 }, +//}; + Color_RGB8 sGauntletColors[] = { { 255, 255, 255 }, { 254, 207, 15 }, @@ -1385,6 +1396,10 @@ s32 Player_OverrideLimbDrawGameplayDefault(PlayState* play, s32 limbIndex, Gfx** sLeftHandType = PLAYER_MODELTYPE_LH_CLOSED; } + if (sLeftHandType != PLAYER_MODELTYPE_LH_OPEN && sLeftHandType != PLAYER_MODELTYPE_LH_CLOSED) { + SkipOverrideNextLimb(); + } + *dList = ResourceMgr_LoadGfxByName(dLists[sDListsLodOffset]); } else if (limbIndex == PLAYER_LIMB_R_HAND) { Gfx** dLists = this->rightHandDLists; @@ -1397,8 +1412,13 @@ s32 Player_OverrideLimbDrawGameplayDefault(PlayState* play, s32 limbIndex, Gfx** sRightHandType = PLAYER_MODELTYPE_RH_CLOSED; } + if (sRightHandType != PLAYER_MODELTYPE_RH_OPEN && sRightHandType != PLAYER_MODELTYPE_RH_CLOSED) { + SkipOverrideNextLimb(); + } + *dList = ResourceMgr_LoadGfxByName(dLists[sDListsLodOffset]); } else if (limbIndex == PLAYER_LIMB_SHEATH) { + SkipOverrideNextLimb(); Gfx** dLists = this->sheathDLists; if ((this->sheathType == PLAYER_MODELTYPE_SHEATH_18) || (this->sheathType == PLAYER_MODELTYPE_SHEATH_19)) { @@ -1462,10 +1482,13 @@ s32 Player_OverrideLimbDrawGameplayFirstPerson(PlayState* play, s32 limbIndex, G } *dList = sFirstPersonLeftHandDLs[handOutDlIndex]; } else if (limbIndex == PLAYER_LIMB_R_SHOULDER) { + SkipOverrideNextLimb(); *dList = sFirstPersonRightShoulderDLs[gSaveContext.linkAge]; } else if (limbIndex == PLAYER_LIMB_R_FOREARM) { + SkipOverrideNextLimb(); *dList = sFirstPersonForearmDLs[gSaveContext.linkAge]; } else if (limbIndex == PLAYER_LIMB_R_HAND) { + SkipOverrideNextLimb(); s32 firstPersonWeaponIndex = gSaveContext.linkAge; if (CVarGetInteger(CVAR_ENHANCEMENT("BowSlingshotAmmoFix"), 0) || CVarGetInteger(CVAR_ENHANCEMENT("EquipmentAlwaysVisible"), 0)) { @@ -1791,6 +1814,55 @@ void Player_PostLimbDrawGameplay(PlayState* play, s32 limbIndex, Gfx** dList, Ve Matrix_MultVec3f(&sZeroVec, D_80160000); } + if (CVarGetInteger("gHoliday.Visual.Hats", 0) && !(this->stateFlags1 & PLAYER_STATE1_FIRST_PERSON) && + !(this->stateFlags2 & PLAYER_STATE2_CRAWLING)) { + if (limbIndex == PLAYER_LIMB_HEAD) { + OPEN_DISPS(play->state.gfxCtx); + + Matrix_Push(); + if (LINK_IS_ADULT) { + Matrix_RotateZYX(24000, -16000, -7000, MTXMODE_APPLY); + Matrix_Translate(32.0f, 0.0f, 0.0f, MTXMODE_APPLY); + Matrix_Scale(1.0f, 1.0f, 1.0f, MTXMODE_APPLY); + gSPMatrix(POLY_OPA_DISP++, MATRIX_NEWMTX(play->state.gfxCtx), + G_MTX_NOPUSH | G_MTX_LOAD | G_MTX_MODELVIEW); + gSPDisplayList(POLY_OPA_DISP++, gLinkAdultHatTrimDL); + } else { + Matrix_RotateZYX(24000, -16000, -7000, MTXMODE_APPLY); + Matrix_Translate(32.0f, 0.0f, 0.0f, MTXMODE_APPLY); + Matrix_Scale(1.0f, 1.0f, 1.0f, MTXMODE_APPLY); + gSPMatrix(POLY_OPA_DISP++, MATRIX_NEWMTX(play->state.gfxCtx), + G_MTX_NOPUSH | G_MTX_LOAD | G_MTX_MODELVIEW); + gSPDisplayList(POLY_OPA_DISP++, gLinkChildHatTrimDL); + } + + Matrix_Pop(); + + CLOSE_DISPS(play->state.gfxCtx); + } + + if (limbIndex == PLAYER_LIMB_HAT) { + OPEN_DISPS(play->state.gfxCtx); + + Matrix_Push(); + if (LINK_IS_ADULT) { + Matrix_RotateZYX(0, 0, 17500, MTXMODE_APPLY); + Matrix_Translate(-195.0f, 1500.0f, -95.0f, MTXMODE_APPLY); + Matrix_Scale(2.0f, 2.0f, 2.0f, MTXMODE_APPLY); + } else { + Matrix_RotateZYX(0, 0, 27000, MTXMODE_APPLY); + Matrix_Translate(-950.0f, 2600.0f, -75.0f, MTXMODE_APPLY); + Matrix_Scale(2.0f, 2.0f, 2.0f, MTXMODE_APPLY); + } + + gSPMatrix(POLY_OPA_DISP++, MATRIX_NEWMTX(play->state.gfxCtx), G_MTX_NOPUSH | G_MTX_LOAD | G_MTX_MODELVIEW); + gSPDisplayList(POLY_OPA_DISP++, gLinkAdultPompomDL); + Matrix_Pop(); + + CLOSE_DISPS(play->state.gfxCtx); + } + } + if (limbIndex == PLAYER_LIMB_L_HAND) { MtxF sp14C; Actor* hookedActor; diff --git a/soh/src/code/z_room.c b/soh/src/code/z_room.c index 172111b5056..37f4e68fef7 100644 --- a/soh/src/code/z_room.c +++ b/soh/src/code/z_room.c @@ -414,8 +414,7 @@ BgImage* func_80096A74(PolygonType1* polygon1, PlayState* play) { camera = GET_ACTIVE_CAM(play); camId = camera->camDataIdx; - if (camId == -1 && (CVarGetInteger(CVAR_CHEAT("NoRestrictItems"), 0) || - (CVarGetInteger(CVAR_REMOTE_CROWD_CONTROL("Enabled"), 0)))) { + if (camId == -1) { // This prevents a crash when using items that change the // camera (such as din's fire), voiding out or dying on // scenes with prerendered backgrounds. diff --git a/soh/src/code/z_skelanime.c b/soh/src/code/z_skelanime.c index a7e93e8c356..c1588ca3923 100644 --- a/soh/src/code/z_skelanime.c +++ b/soh/src/code/z_skelanime.c @@ -5,6 +5,7 @@ #include #include "soh/ResourceManagerHelpers.h" #include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h" +#include "soh/Enhancements/Holiday/Archez.h" #define ANIM_INTERP 1 @@ -45,7 +46,7 @@ void SkelAnime_DrawLimbLod(PlayState* play, s32 limbIndex, void** skeleton, Vec3 if ((overrideLimbDraw == NULL) || !overrideLimbDraw(play, limbIndex, &dList, &pos, &rot, arg)) { Matrix_TranslateRotateZYX(&pos, &rot); - if (dList != NULL) { + if (GameInteractor_Should(VB_DRAW_SKEL_LIMB, dList != NULL, &POLY_OPA_DISP, dList)) { gSPMatrix(POLY_OPA_DISP++, MATRIX_NEWMTX(play->state.gfxCtx), G_MTX_LOAD); gSPDisplayList(POLY_OPA_DISP++, dList); } @@ -105,7 +106,7 @@ void SkelAnime_DrawLod(PlayState* play, void** skeleton, Vec3s* jointTable, Over if ((overrideLimbDraw == NULL) || !overrideLimbDraw(play, 1, &dList, &pos, &rot, arg)) { Matrix_TranslateRotateZYX(&pos, &rot); - if (dList != NULL) { + if (GameInteractor_Should(VB_DRAW_SKEL_LIMB, dList != NULL, &POLY_OPA_DISP, dList)) { gSPMatrix(POLY_OPA_DISP++, MATRIX_NEWMTX(play->state.gfxCtx), G_MTX_LOAD); gSPDisplayList(POLY_OPA_DISP++, dList); } @@ -121,6 +122,8 @@ void SkelAnime_DrawLod(PlayState* play, void** skeleton, Vec3s* jointTable, Over Matrix_Pop(); CLOSE_DISPS(play->state.gfxCtx); + + ClearOverrideSkips(); } /** @@ -157,8 +160,10 @@ void SkelAnime_DrawFlexLimbLod(PlayState* play, s32 limbIndex, void** skeleton, MATRIX_TOMTX(*mtx); { OPEN_DISPS(play->state.gfxCtx); - gSPMatrix(POLY_OPA_DISP++, *mtx, G_MTX_LOAD); - gSPDisplayList(POLY_OPA_DISP++, newDList); + if (GameInteractor_Should(VB_DRAW_SKEL_LIMB, true, &POLY_OPA_DISP, newDList, *mtx)) { + gSPMatrix(POLY_OPA_DISP++, *mtx, G_MTX_LOAD); + gSPDisplayList(POLY_OPA_DISP++, newDList); + } CLOSE_DISPS(play->state.gfxCtx); } (*mtx)++; @@ -230,8 +235,10 @@ void SkelAnime_DrawFlexLod(PlayState* play, void** skeleton, Vec3s* jointTable, if (newDList != NULL) { MATRIX_TOMTX(mtx); gDPNoOpString(POLY_OPA_DISP++, "T5ST", 0); - gSPMatrix(POLY_OPA_DISP++, mtx, G_MTX_LOAD); - gSPDisplayList(POLY_OPA_DISP++, newDList); + if (GameInteractor_Should(VB_DRAW_SKEL_LIMB, true, &POLY_OPA_DISP, newDList, *mtx)) { + gSPMatrix(POLY_OPA_DISP++, mtx, G_MTX_LOAD); + gSPDisplayList(POLY_OPA_DISP++, newDList); + } mtx++; } else if (limbDList != NULL) { MATRIX_TOMTX(mtx); @@ -250,6 +257,8 @@ void SkelAnime_DrawFlexLod(PlayState* play, void** skeleton, Vec3s* jointTable, Matrix_Pop(); CLOSE_DISPS(play->state.gfxCtx); + + ClearOverrideSkips(); } /** @@ -275,7 +284,7 @@ void SkelAnime_DrawLimbOpa(PlayState* play, s32 limbIndex, void** skeleton, Vec3 if ((overrideLimbDraw == NULL) || !overrideLimbDraw(play, limbIndex, &dList, &pos, &rot, arg)) { Matrix_TranslateRotateZYX(&pos, &rot); - if (dList != NULL) { + if (GameInteractor_Should(VB_DRAW_SKEL_LIMB, dList != NULL, &POLY_OPA_DISP, dList)) { gSPMatrix(POLY_OPA_DISP++, MATRIX_NEWMTX(play->state.gfxCtx), G_MTX_LOAD); gSPDisplayList(POLY_OPA_DISP++, dList); } @@ -356,7 +365,7 @@ void SkelAnime_DrawOpa(PlayState* play, void** skeleton, Vec3s* jointTable, Over if ((overrideLimbDraw == NULL) || !overrideLimbDraw(play, 1, &dList, &pos, &rot, arg)) { Matrix_TranslateRotateZYX(&pos, &rot); - if (dList != NULL) { + if (GameInteractor_Should(VB_DRAW_SKEL_LIMB, dList != NULL, &POLY_OPA_DISP, dList)) { gSPMatrix(POLY_OPA_DISP++, MATRIX_NEWMTX(play->state.gfxCtx), G_MTX_LOAD); gSPDisplayList(POLY_OPA_DISP++, dList); } @@ -373,6 +382,8 @@ void SkelAnime_DrawOpa(PlayState* play, void** skeleton, Vec3s* jointTable, Over Matrix_Pop(); CLOSE_DISPS(play->state.gfxCtx); + + ClearOverrideSkips(); } /** @@ -407,8 +418,10 @@ void SkelAnime_DrawFlexLimbOpa(PlayState* play, s32 limbIndex, void** skeleton, Matrix_TranslateRotateZYX(&pos, &rot); if (newDList != NULL) { MATRIX_TOMTX(*limbMatricies); - gSPMatrix(POLY_OPA_DISP++, *limbMatricies, G_MTX_LOAD); - gSPDisplayList(POLY_OPA_DISP++, newDList); + if (GameInteractor_Should(VB_DRAW_SKEL_LIMB, true, &POLY_OPA_DISP, newDList, *limbMatricies)) { + gSPMatrix(POLY_OPA_DISP++, *limbMatricies, G_MTX_LOAD); + gSPDisplayList(POLY_OPA_DISP++, newDList); + } (*limbMatricies)++; } else if (limbDList != NULL) { MATRIX_TOMTX(*limbMatricies); @@ -476,8 +489,10 @@ void SkelAnime_DrawFlexOpa(PlayState* play, void** skeleton, Vec3s* jointTable, Matrix_TranslateRotateZYX(&pos, &rot); if (newDList != NULL) { MATRIX_TOMTX(mtx); - gSPMatrix(POLY_OPA_DISP++, mtx, G_MTX_LOAD); - gSPDisplayList(POLY_OPA_DISP++, newDList); + if (GameInteractor_Should(VB_DRAW_SKEL_LIMB, true, &POLY_OPA_DISP, newDList, *mtx)) { + gSPMatrix(POLY_OPA_DISP++, mtx, G_MTX_LOAD); + gSPDisplayList(POLY_OPA_DISP++, newDList); + } mtx++; } else if (limbDList != NULL) { MATRIX_TOMTX(mtx); @@ -496,6 +511,8 @@ void SkelAnime_DrawFlexOpa(PlayState* play, void** skeleton, Vec3s* jointTable, Matrix_Pop(); CLOSE_DISPS(play->state.gfxCtx); + + ClearOverrideSkips(); } /** @@ -574,7 +591,7 @@ Gfx* SkelAnime_DrawLimb(PlayState* play, s32 limbIndex, void** skeleton, Vec3s* if ((overrideLimbDraw == NULL) || !overrideLimbDraw(play, limbIndex, &dList, &pos, &rot, arg, &gfx)) { Matrix_TranslateRotateZYX(&pos, &rot); - if (dList != NULL) { + if (GameInteractor_Should(VB_DRAW_SKEL_LIMB, dList != NULL, &gfx, dList)) { gSPMatrix(gfx++, MATRIX_NEWMTX(play->state.gfxCtx), G_MTX_LOAD); gSPDisplayList(gfx++, dList); } @@ -630,7 +647,7 @@ Gfx* SkelAnime_Draw(PlayState* play, void** skeleton, Vec3s* jointTable, Overrid if ((overrideLimbDraw == NULL) || !overrideLimbDraw(play, 1, &dList, &pos, &rot, arg, &gfx)) { Matrix_TranslateRotateZYX(&pos, &rot); - if (dList != NULL) { + if (GameInteractor_Should(VB_DRAW_SKEL_LIMB, dList != NULL, &gfx, dList)) { gSPMatrix(gfx++, MATRIX_NEWMTX(play->state.gfxCtx), G_MTX_LOAD); gSPDisplayList(gfx++, dList); } @@ -646,6 +663,8 @@ Gfx* SkelAnime_Draw(PlayState* play, void** skeleton, Vec3s* jointTable, Overrid Matrix_Pop(); + ClearOverrideSkips(); + return gfx; } @@ -676,8 +695,10 @@ Gfx* SkelAnime_DrawFlexLimb(PlayState* play, s32 limbIndex, void** skeleton, Vec Matrix_TranslateRotateZYX(&pos, &rot); if (newDList != NULL) { MATRIX_TOMTX(*mtx); - gSPMatrix(gfx++, *mtx, G_MTX_LOAD); - gSPDisplayList(gfx++, newDList); + if (GameInteractor_Should(VB_DRAW_SKEL_LIMB, true, &gfx, newDList, *mtx)) { + gSPMatrix(gfx++, *mtx, G_MTX_LOAD); + gSPDisplayList(gfx++, newDList); + } (*mtx)++; } else if (limbDList != NULL) { MATRIX_TOMTX(*mtx); @@ -741,8 +762,10 @@ Gfx* SkelAnime_DrawFlex(PlayState* play, void** skeleton, Vec3s* jointTable, s32 Matrix_TranslateRotateZYX(&pos, &rot); if (newDList != NULL) { MATRIX_TOMTX(mtx); - gSPMatrix(gfx++, mtx, G_MTX_LOAD); - gSPDisplayList(gfx++, newDList); + if (GameInteractor_Should(VB_DRAW_SKEL_LIMB, true, &gfx, newDList, *mtx)) { + gSPMatrix(gfx++, mtx, G_MTX_LOAD); + gSPDisplayList(gfx++, newDList); + } mtx++; } else if (limbDList != NULL) { MATRIX_TOMTX(mtx); @@ -759,6 +782,8 @@ Gfx* SkelAnime_DrawFlex(PlayState* play, void** skeleton, Vec3s* jointTable, s32 Matrix_Pop(); + ClearOverrideSkips(); + return gfx; } @@ -889,6 +914,8 @@ void AnimationContext_SetLoadFrame(PlayState* play, LinkAnimationHeader* animati Vec3s* frameTable) { AnimationEntry* entry = AnimationContext_AddEntry(&play->animationCtx, ANIMENTRY_LOADFRAME); + GameInteractor_ExecuteOnAnimationSetLoadFrame(animation, &frame); + if (GameInteractor_Should(VB_LOAD_PLAYER_ANIMATION_FRAME, entry != NULL, entry, animation, frame, limbCount, frameTable)) { if (ResourceMgr_OTRSigCheck(animation) != 0) @@ -1225,6 +1252,7 @@ s32 LinkAnimation_Once(PlayState* play, SkelAnime* skelAnime) { if (skelAnime->curFrame == skelAnime->endFrame) { LinkAnimation_AnimateFrame(play, skelAnime); + GameInteractor_ExecuteOnLinkAnimEnd(skelAnime); return 1; } skelAnime->curFrame += skelAnime->playSpeed * updateRate; diff --git a/soh/src/code/z_sram.c b/soh/src/code/z_sram.c index 7abc221a4aa..a47b6facbca 100644 --- a/soh/src/code/z_sram.c +++ b/soh/src/code/z_sram.c @@ -8,6 +8,7 @@ #include "soh/OTRGlobals.h" #include "soh/SaveManager.h" #include "soh/ResourceManagerHelpers.h" +#include "soh/Network/Archipelago/Archipelago.h" #define NUM_DUNGEONS 8 #define NUM_COWS 10 @@ -261,10 +262,19 @@ void Sram_InitSave(FileChooseContext* fileChooseCtx) { u8 currentQuest = fileChooseCtx->questType[fileChooseCtx->buttonIndex]; - if (currentQuest == QUEST_RANDOMIZER && (Randomizer_IsSeedGenerated() || Randomizer_IsSpoilerLoaded())) { + // Temporary + if (CVarGetInteger(CVAR_REMOTE_ARCHIPELAGO("Connected"), 0)) { + currentQuest = QUEST_ARCHIPELAGO; + } + + if ((currentQuest == QUEST_RANDOMIZER && (Randomizer_IsSeedGenerated() || Randomizer_IsSpoilerLoaded())) || + currentQuest == QUEST_ARCHIPELAGO) { gSaveContext.ship.quest.id = QUEST_RANDOMIZER; Randomizer_InitSaveFile(); + if (currentQuest == QUEST_ARCHIPELAGO) { + Archipelago_InitSaveFile(); + } } else { gSaveContext.ship.quest.id = currentQuest; } diff --git a/soh/src/overlays/actors/ovl_Bg_Spot01_Idosoko/z_bg_spot01_idosoko.c b/soh/src/overlays/actors/ovl_Bg_Spot01_Idosoko/z_bg_spot01_idosoko.c index 8b3480d87ab..a3b75ad4b00 100644 --- a/soh/src/overlays/actors/ovl_Bg_Spot01_Idosoko/z_bg_spot01_idosoko.c +++ b/soh/src/overlays/actors/ovl_Bg_Spot01_Idosoko/z_bg_spot01_idosoko.c @@ -51,7 +51,9 @@ void BgSpot01Idosoko_Init(Actor* thisx, PlayState* play) { // If dungeon entrance randomizer is on, remove the well stone as adult Link when // child Link has drained the water to the well if (!LINK_IS_ADULT || - (IS_RANDO && Randomizer_GetSettingValue(RSK_SHUFFLE_DUNGEON_ENTRANCES) != RO_DUNGEON_ENTRANCE_SHUFFLE_OFF && + (IS_RANDO && + (Randomizer_GetSettingValue(RSK_SHUFFLE_DUNGEON_ENTRANCES) != RO_DUNGEON_ENTRANCE_SHUFFLE_OFF || + CVarGetInteger(CVAR_REMOTE_ARCHIPELAGO("ShuffleEntrances"), 0)) && Flags_GetEventChkInf(EVENTCHKINF_DRAINED_WELL_IN_KAKARIKO))) { Actor_Kill(&this->dyna.actor); } else { diff --git a/soh/src/overlays/actors/ovl_Bg_Spot08_Iceblock/z_bg_spot08_iceblock.c b/soh/src/overlays/actors/ovl_Bg_Spot08_Iceblock/z_bg_spot08_iceblock.c index a85ec4f515c..1c97aa17c36 100644 --- a/soh/src/overlays/actors/ovl_Bg_Spot08_Iceblock/z_bg_spot08_iceblock.c +++ b/soh/src/overlays/actors/ovl_Bg_Spot08_Iceblock/z_bg_spot08_iceblock.c @@ -307,7 +307,7 @@ void BgSpot08Iceblock_Init(Actor* thisx, PlayState* play) { break; } - if (LINK_AGE_IN_YEARS == YEARS_CHILD) { + if (LINK_AGE_IN_YEARS == YEARS_CHILD && play->sceneNum == SCENE_ZORAS_FOUNTAIN) { Actor_Kill(&this->dyna.actor); return; } @@ -331,6 +331,12 @@ void BgSpot08Iceblock_Init(Actor* thisx, PlayState* play) { this->surfaceNormal.y = 1.0f; this->rotationAxis.x = 1.0f; + if (LINK_IS_ADULT && !Flags_GetEventChkInf(EVENTCHKINF_RAISED_LAKE_HYLIA_WATER) && + play->sceneNum == SCENE_LAKE_HYLIA) { + BgSpot08Iceblock_SetupNoAction(this); + return; + } + switch (this->dyna.actor.params & 0xF) { case 0: case 1: diff --git a/soh/src/overlays/actors/ovl_Boss_Dodongo/z_boss_dodongo.c b/soh/src/overlays/actors/ovl_Boss_Dodongo/z_boss_dodongo.c index acba457abfe..bc145eca3ca 100644 --- a/soh/src/overlays/actors/ovl_Boss_Dodongo/z_boss_dodongo.c +++ b/soh/src/overlays/actors/ovl_Boss_Dodongo/z_boss_dodongo.c @@ -6,6 +6,7 @@ #include "soh/frame_interpolation.h" #include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h" #include "soh/OTRGlobals.h" +#include "soh_assets.h" #include "soh/ResourceManagerHelpers.h" #include // malloc @@ -1357,6 +1358,21 @@ void BossDodongo_PostLimbDraw(PlayState* play, s32 limbIndex, Gfx** dList, Vec3s Matrix_MultVec3f(&D_808CA48C, &this->unk_404); } Collider_UpdateSpheres(limbIndex, &this->collider); + + if (CVarGetInteger("gHoliday.Visual.Hats", 0)) { + if (limbIndex == 7) { + OPEN_DISPS(play->state.gfxCtx); + Matrix_Push(); + Matrix_RotateZYX(-6643, 1771, -14834, MTXMODE_APPLY); + Matrix_Translate(2000.0f, 5000.0f, 4000.0f, MTXMODE_APPLY); + Matrix_Scale(6.114f, 6.114f, 6.114f, MTXMODE_APPLY); + gDPSetEnvColor(POLY_OPA_DISP++, 255, 0, 0, 255); + gSPMatrix(POLY_OPA_DISP++, MATRIX_NEWMTX(play->state.gfxCtx), G_MTX_NOPUSH | G_MTX_LOAD | G_MTX_MODELVIEW); + gSPDisplayList(POLY_OPA_DISP++, gPaperCrownGenericDL); + Matrix_Pop(); + CLOSE_DISPS(play->state.gfxCtx); + } + } } void BossDodongo_Draw(Actor* thisx, PlayState* play) { diff --git a/soh/src/overlays/actors/ovl_Boss_Ganon/z_boss_ganon.c b/soh/src/overlays/actors/ovl_Boss_Ganon/z_boss_ganon.c index 534988630bc..7e21b8e9946 100644 --- a/soh/src/overlays/actors/ovl_Boss_Ganon/z_boss_ganon.c +++ b/soh/src/overlays/actors/ovl_Boss_Ganon/z_boss_ganon.c @@ -9,6 +9,7 @@ #include "assets/objects/object_ganon_anime1/object_ganon_anime1.h" #include "assets/objects/object_ganon_anime2/object_ganon_anime2.h" #include "assets/scenes/dungeons/ganon_boss/ganon_boss_scene.h" +#include "soh_assets.h" #include "soh/frame_interpolation.h" #include "soh/OTRGlobals.h" @@ -572,7 +573,8 @@ void BossGanon_IntroCutscene(BossGanon* this, PlayState* play) { Play_ChangeCameraStatus(play, this->csCamIndex, CAM_STAT_ACTIVE); this->csCamFov = 60.0f; - if (Flags_GetEventChkInf(EVENTCHKINF_BEGAN_GANONDORF_BATTLE) || IS_RANDO || IS_BOSS_RUSH) { + if (Flags_GetEventChkInf(EVENTCHKINF_BEGAN_GANONDORF_BATTLE) || IS_RANDO || IS_BOSS_RUSH || + CVarGetInteger("gHoliday.NotProxySaw.GanonDatingSim", 0)) { // watched cutscene already, skip most of it this->csState = 17; this->csTimer = 0; @@ -926,6 +928,9 @@ void BossGanon_IntroCutscene(BossGanon* this, PlayState* play) { if ((this->csTimer <= 50) || (Message_GetState(&play->msgCtx) != TEXT_STATE_NONE)) { break; } + if (!GameInteractor_Should(VB_GANONDORF_DECIDE_TO_FIGHT, true)) { + break; + } this->csState = 19; this->csTimer = 0; @@ -3378,6 +3383,21 @@ void BossGanon_PostLimbDraw(PlayState* play, s32 limbIndex, Gfx** dList, Vec3s* Matrix_MultVec3f(&sp1C, &this->unk_214); } + + if (limbIndex == 14) { + OPEN_DISPS(play->state.gfxCtx); + Matrix_Push(); + Matrix_RotateZYX(7749, 0, -11956, MTXMODE_APPLY); + Matrix_Translate(675.676f, -229.730f, 148.649f, MTXMODE_APPLY); + Matrix_Scale(1.014f, 1.014f, 1.014f, MTXMODE_APPLY); + + gSPMatrix(POLY_OPA_DISP++, MATRIX_NEWMTX(play->state.gfxCtx), G_MTX_NOPUSH | G_MTX_LOAD | G_MTX_MODELVIEW); + Color_RGBA8 color = { 255, 0, 0, 255 }; + gDPSetEnvColor(POLY_OPA_DISP++, color.r, color.g, color.b, color.a); + gSPDisplayList(POLY_OPA_DISP++, gPaperCrownGenericDL); + Matrix_Pop(); + CLOSE_DISPS(play->state.gfxCtx); + } } void BossGanon_InitRand(s32 seedInit0, s32 seedInit1, s32 seedInit2) { diff --git a/soh/src/overlays/actors/ovl_Boss_Ganon2/z_boss_ganon2.c b/soh/src/overlays/actors/ovl_Boss_Ganon2/z_boss_ganon2.c index 1799dafd0c4..95549b09a27 100644 --- a/soh/src/overlays/actors/ovl_Boss_Ganon2/z_boss_ganon2.c +++ b/soh/src/overlays/actors/ovl_Boss_Ganon2/z_boss_ganon2.c @@ -10,6 +10,7 @@ #include "soh/OTRGlobals.h" #include "soh/ResourceManagerHelpers.h" #include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h" +#include "soh_assets.h" #include @@ -2649,6 +2650,18 @@ void BossGanon2_PostLimbDraw(PlayState* play, s32 limbIndex, Gfx** dList, Vec3s* } } + if (CVarGetInteger("gHoliday.Visual.Hats", 0)) { + if (limbIndex == 16) { + Matrix_Push(); + Matrix_RotateZYX(5977, 4649, 18154, MTXMODE_APPLY); + Matrix_Translate(364.865f, 67.568f, 378.378f, MTXMODE_APPLY); + Matrix_Scale(4.595f, 4.595f, 4.595f, MTXMODE_APPLY); + gSPMatrix(POLY_OPA_DISP++, MATRIX_NEWMTX(play->state.gfxCtx), G_MTX_NOPUSH | G_MTX_LOAD | G_MTX_MODELVIEW); + gSPDisplayList(POLY_OPA_DISP++, gSantaHatGenericDL); + Matrix_Pop(); + } + } + CLOSE_DISPS(play->state.gfxCtx); } @@ -2768,6 +2781,21 @@ void BossGanon2_PostLimbDraw2(PlayState* play, s32 limbIndex, Gfx** dList, Vec3s } else if (limbIndex == 10) { Matrix_MultVec3f(&D_80907164, &this->unk_1B8); } + + if (CVarGetInteger("gHoliday.Visual.Hats", 0)) { + if (limbIndex == 11) { + OPEN_DISPS(play->state.gfxCtx); + Matrix_Push(); + Matrix_RotateZYX(0, 0, -15056, MTXMODE_APPLY); + Matrix_Translate(824.324f, 472.973f, 0.0f, MTXMODE_APPLY); + Matrix_Scale(0.845f, 0.845f, 0.845f, MTXMODE_APPLY); + gDPSetEnvColor(POLY_OPA_DISP++, 255, 100, 100, 255); + gSPMatrix(POLY_OPA_DISP++, MATRIX_NEWMTX(play->state.gfxCtx), G_MTX_NOPUSH | G_MTX_LOAD | G_MTX_MODELVIEW); + gSPDisplayList(POLY_OPA_DISP++, gPaperCrownGenericDL); + Matrix_Pop(); + CLOSE_DISPS(play->state.gfxCtx); + } + } } void func_80905674(BossGanon2* this, PlayState* play) { diff --git a/soh/src/overlays/actors/ovl_Boss_Ganondrof/z_boss_ganondrof.c b/soh/src/overlays/actors/ovl_Boss_Ganondrof/z_boss_ganondrof.c index 8f74d05a851..a67636d065a 100644 --- a/soh/src/overlays/actors/ovl_Boss_Ganondrof/z_boss_ganondrof.c +++ b/soh/src/overlays/actors/ovl_Boss_Ganondrof/z_boss_ganondrof.c @@ -12,7 +12,9 @@ #include "overlays/effects/ovl_Effect_Ss_Hahen/z_eff_ss_hahen.h" #include "overlays/actors/ovl_Door_Warp1/z_door_warp1.h" #include "soh/OTRGlobals.h" +#include "soh_assets.h" #include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h" +#include "soh/Enhancements/Holiday/Archez.h" #define FLAGS \ (ACTOR_FLAG_ATTENTION_ENABLED | ACTOR_FLAG_HOSTILE | ACTOR_FLAG_UPDATE_CULLING_DISABLED | \ @@ -1350,6 +1352,8 @@ s32 BossGanondrof_OverrideLimbDraw(PlayState* play, s32 limbIndex, Gfx** dList, case 15: if ((this->actionFunc == BossGanondrof_Intro) && this->work[GND_MASK_OFF]) { *dList = gPhantomGanonFaceDL; + } else { + SkipOverrideNextLimb(); } rot->y += this->rideRotY[limbIndex]; rot->z += this->rideRotZ[limbIndex]; @@ -1409,6 +1413,10 @@ s32 BossGanondrof_OverrideLimbDraw(PlayState* play, s32 limbIndex, Gfx** dList, break; } + if (limbIndex == 12) { + SkipOverrideNextLimb(); + } + return 0; } @@ -1427,6 +1435,21 @@ void BossGanondrof_PostLimbDraw(PlayState* play, s32 limbIndex, Gfx** dList, Vec if (((this->flyMode != GND_FLY_PAINTING) || (this->actionFunc == BossGanondrof_Intro)) && (limbIndex <= 25)) { Matrix_MultVec3f(&zeroVec, &this->bodyPartsPos[limbIndex - 1]); } + + if (CVarGetInteger("gHoliday.Visual.Hats", 0) && this->deathState == NOT_DEAD) { + if (limbIndex == 15) { + OPEN_DISPS(play->state.gfxCtx); + Matrix_Push(); + Matrix_RotateZYX(11955, 0, -15499, MTXMODE_APPLY); + Matrix_Translate(459.460f, 256.757f, -567.568f, MTXMODE_APPLY); + Matrix_Scale(0.877f, 0.877f, 0.877f, MTXMODE_APPLY); + gDPSetEnvColor(POLY_OPA_DISP++, 255, 100, 100, 255); + gSPMatrix(POLY_OPA_DISP++, MATRIX_NEWMTX(play->state.gfxCtx), G_MTX_NOPUSH | G_MTX_LOAD | G_MTX_MODELVIEW); + gSPDisplayList(POLY_OPA_DISP++, gPaperCrownGenericDL); + Matrix_Pop(); + CLOSE_DISPS(play->state.gfxCtx); + } + } } Gfx* BossGanondrof_GetClearPixelDList(GraphicsContext* gfxCtx) { diff --git a/soh/src/overlays/actors/ovl_Boss_Goma/z_boss_goma.c b/soh/src/overlays/actors/ovl_Boss_Goma/z_boss_goma.c index 503e4147247..2982e72911f 100644 --- a/soh/src/overlays/actors/ovl_Boss_Goma/z_boss_goma.c +++ b/soh/src/overlays/actors/ovl_Boss_Goma/z_boss_goma.c @@ -8,6 +8,8 @@ #include "soh/Enhancements/game-interactor/GameInteractor.h" #include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h" +#include "soh/Enhancements/Holiday/Archez.h" + #define FLAGS \ (ACTOR_FLAG_ATTENTION_ENABLED | ACTOR_FLAG_HOSTILE | ACTOR_FLAG_UPDATE_CULLING_DISABLED | \ ACTOR_FLAG_DRAW_CULLING_DISABLED) @@ -1981,6 +1983,7 @@ s32 BossGoma_OverrideLimbDraw(PlayState* play, s32 limbIndex, Gfx** dList, Vec3f switch (limbIndex) { case BOSSGOMA_LIMB_EYE: + SkipOverrideNextLimb(); if (this->eyeState == EYESTATE_IRIS_FOLLOW_BONUS_IFRAMES && this->eyeLidBottomRotX < -0xA8C) { *dList = NULL; } else if (this->invincibilityFrames != 0) { @@ -1993,10 +1996,12 @@ s32 BossGoma_OverrideLimbDraw(PlayState* play, s32 limbIndex, Gfx** dList, Vec3f break; case BOSSGOMA_LIMB_EYE_LID_BOTTOM_ROOT2: + SkipOverrideNextLimb(); rot->x += this->eyeLidBottomRotX; break; case BOSSGOMA_LIMB_EYE_LID_TOP_ROOT2: + SkipOverrideNextLimb(); rot->x += this->eyeLidTopRotX; break; diff --git a/soh/src/overlays/actors/ovl_Boss_Sst/z_boss_sst.c b/soh/src/overlays/actors/ovl_Boss_Sst/z_boss_sst.c index 6e1db9049a4..20b3258247f 100644 --- a/soh/src/overlays/actors/ovl_Boss_Sst/z_boss_sst.c +++ b/soh/src/overlays/actors/ovl_Boss_Sst/z_boss_sst.c @@ -15,6 +15,8 @@ #include "soh/Enhancements/game-interactor/GameInteractor.h" #include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h" +#include "soh/Enhancements/Holiday/Archez.h" + #define FLAGS \ (ACTOR_FLAG_ATTENTION_ENABLED | ACTOR_FLAG_HOSTILE | ACTOR_FLAG_UPDATE_CULLING_DISABLED | \ ACTOR_FLAG_DRAW_CULLING_DISABLED | ACTOR_FLAG_HOOKSHOT_PULLS_PLAYER) @@ -2865,6 +2867,11 @@ s32 BossSst_OverrideHeadDraw(PlayState* play, s32 limbIndex, Gfx** dList, Vec3f* rot->z -= 0x200; } } + + if (limbIndex == 7) { + SkipOverrideNextLimb(); + } + return false; } diff --git a/soh/src/overlays/actors/ovl_Boss_Tw/z_boss_tw.c b/soh/src/overlays/actors/ovl_Boss_Tw/z_boss_tw.c index 1fee8de966f..e5860c1fc72 100644 --- a/soh/src/overlays/actors/ovl_Boss_Tw/z_boss_tw.c +++ b/soh/src/overlays/actors/ovl_Boss_Tw/z_boss_tw.c @@ -8,6 +8,7 @@ #include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h" #include +#include "soh/Enhancements/Holiday/Archez.h" #define FLAGS \ (ACTOR_FLAG_ATTENTION_ENABLED | ACTOR_FLAG_HOSTILE | ACTOR_FLAG_UPDATE_CULLING_DISABLED | \ @@ -3194,6 +3195,10 @@ s32 BossTw_OverrideLimbDraw(PlayState* play, s32 limbIndex, Gfx** dList, Vec3f* } } + if (limbIndex == 14) { + SkipOverrideNextLimb(); + } + return false; } @@ -3610,6 +3615,10 @@ s32 BossTw_TwinrovaOverrideLimbDraw(PlayState* play, s32 limbIndex, Gfx** dList, *dList = NULL; } + if (limbIndex == 34 || limbIndex == 40) { + SkipOverrideNextLimb(); + } + CLOSE_DISPS(play->state.gfxCtx); return false; diff --git a/soh/src/overlays/actors/ovl_Boss_Va/z_boss_va.c b/soh/src/overlays/actors/ovl_Boss_Va/z_boss_va.c index 3e8f05776e6..43b18cb68cb 100644 --- a/soh/src/overlays/actors/ovl_Boss_Va/z_boss_va.c +++ b/soh/src/overlays/actors/ovl_Boss_Va/z_boss_va.c @@ -19,6 +19,8 @@ #include "soh/Enhancements/game-interactor/GameInteractor.h" #include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h" +#include "soh/Enhancements/Holiday/Archez.h" + #define FLAGS \ (ACTOR_FLAG_ATTENTION_ENABLED | ACTOR_FLAG_HOSTILE | ACTOR_FLAG_UPDATE_CULLING_DISABLED | \ ACTOR_FLAG_DRAW_CULLING_DISABLED) @@ -3253,6 +3255,7 @@ void BossVa_Draw(Actor* thisx, PlayState* play) { break; default: if (!this->isDead) { + SkipOverrideNextSkeleton(); SkelAnime_DrawSkeletonOpa(play, &this->skelAnime, BossVa_BariOverrideLimbDraw, BossVa_BariPostLimbDraw, this); Collider_UpdateSpheres(0, &this->colliderSph); diff --git a/soh/src/overlays/actors/ovl_Door_Ana/z_door_ana.c b/soh/src/overlays/actors/ovl_Door_Ana/z_door_ana.c index 7dde93e1cd9..a0455fbe9d5 100644 --- a/soh/src/overlays/actors/ovl_Door_Ana/z_door_ana.c +++ b/soh/src/overlays/actors/ovl_Door_Ana/z_door_ana.c @@ -181,7 +181,7 @@ void DoorAna_Update(Actor* thisx, PlayState* play) { this->actionFunc(this, play); // Changes the grottos facing angle based on camera angle - if (!CVarGetInteger(CVAR_ENHANCEMENT("DisableGrottoRotation"), 0)) { + if (!CVarGetInteger(CVAR_ENHANCEMENT("DisableGrottoRotation"), 0) && thisx->draw == DoorAna_Draw) { this->actor.shape.rot.y = Camera_GetCamDirYaw(GET_ACTIVE_CAM(play)) + 0x8000; } } diff --git a/soh/src/overlays/actors/ovl_Door_Killer/z_door_killer.c b/soh/src/overlays/actors/ovl_Door_Killer/z_door_killer.c index 15bf27f4390..6bce6514e51 100644 --- a/soh/src/overlays/actors/ovl_Door_Killer/z_door_killer.c +++ b/soh/src/overlays/actors/ovl_Door_Killer/z_door_killer.c @@ -13,6 +13,8 @@ #include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h" #include "soh/ResourceManagerHelpers.h" +#include "soh/Enhancements/Holiday/Archez.h" + #define FLAGS ACTOR_FLAG_UPDATE_CULLING_DISABLED typedef enum { @@ -533,6 +535,7 @@ void DoorKiller_DrawDoor(Actor* thisx, PlayState* play) { Gfx_SetupDL_37Opa(play->state.gfxCtx); DoorKiller_SetTexture(&this->actor, play); + SkipOverrideNextSkeleton(); SkelAnime_DrawSkeletonOpa(play, &this->skelAnime, NULL, NULL, NULL); } diff --git a/soh/src/overlays/actors/ovl_Door_Shutter/z_door_shutter.h b/soh/src/overlays/actors/ovl_Door_Shutter/z_door_shutter.h index 6916f88326a..156837e9d43 100644 --- a/soh/src/overlays/actors/ovl_Door_Shutter/z_door_shutter.h +++ b/soh/src/overlays/actors/ovl_Door_Shutter/z_door_shutter.h @@ -58,4 +58,6 @@ typedef struct DoorShutter { /* 0x0174 */ DoorShutterActionFunc actionFunc; } DoorShutter; // size = 0x0178 +void DoorShutter_SetupType(DoorShutter* DoorShutter, PlayState* play); + #endif diff --git a/soh/src/overlays/actors/ovl_En_Am/z_en_am.c b/soh/src/overlays/actors/ovl_En_Am/z_en_am.c index 272d5df0ccf..e00d12926b0 100644 --- a/soh/src/overlays/actors/ovl_En_Am/z_en_am.c +++ b/soh/src/overlays/actors/ovl_En_Am/z_en_am.c @@ -9,6 +9,9 @@ #include "overlays/actors/ovl_En_Bom/z_en_bom.h" #include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h" #include "soh/ResourceManagerHelpers.h" +#include "soh_assets.h" + +#include "soh/Enhancements/Holiday/Archez.h" #define FLAGS \ (ACTOR_FLAG_ATTENTION_ENABLED | ACTOR_FLAG_HOSTILE | ACTOR_FLAG_UPDATE_CULLING_DISABLED | \ @@ -952,6 +955,21 @@ void EnAm_PostLimbDraw(PlayState* play, s32 limbIndex, Gfx** dList, Vec3s* rot, if ((limbIndex == 1) && (this->unk_264 != 0)) { EnAm_TransformSwordHitbox(&this->dyna.actor, play); } + + if (CVarGetInteger("gHoliday.Visual.Hats", 0)) { + if (limbIndex == 4) { + OPEN_DISPS(play->state.gfxCtx); + Matrix_Push(); + Matrix_RotateZYX(0, 0, -2657, MTXMODE_APPLY); + Matrix_Translate(4000.0f, 1148.649f, 0.0f, MTXMODE_APPLY); + Matrix_Scale(1.655f, 1.655f, 1.655f, MTXMODE_APPLY); + gDPSetEnvColor(POLY_OPA_DISP++, 255, 255, 0, 255); + gSPMatrix(POLY_OPA_DISP++, MATRIX_NEWMTX(play->state.gfxCtx), G_MTX_NOPUSH | G_MTX_LOAD | G_MTX_MODELVIEW); + gSPDisplayList(POLY_OPA_DISP++, gPaperCrownGenericDL); + Matrix_Pop(); + CLOSE_DISPS(play->state.gfxCtx); + } + } } static Vec3f sIcePosOffsets[] = { @@ -969,6 +987,7 @@ void EnAm_Draw(Actor* thisx, PlayState* play) { Gfx_SetupDL_25Opa(play->state.gfxCtx); gDPSetEnvColor(POLY_OPA_DISP++, 0, 0, 0, this->textureBlend); + SkipOverrideNextSkeleton(); SkelAnime_DrawSkeletonOpa(play, &this->skelAnime, NULL, EnAm_PostLimbDraw, this); if (this->iceTimer != 0) { diff --git a/soh/src/overlays/actors/ovl_En_Ani/z_en_ani.c b/soh/src/overlays/actors/ovl_En_Ani/z_en_ani.c index 7897ef7619a..9ee8ff57089 100644 --- a/soh/src/overlays/actors/ovl_En_Ani/z_en_ani.c +++ b/soh/src/overlays/actors/ovl_En_Ani/z_en_ani.c @@ -9,6 +9,7 @@ #include "soh/OTRGlobals.h" #include "soh/ResourceManagerHelpers.h" #include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h" +#include "soh_assets.h" #define FLAGS (ACTOR_FLAG_ATTENTION_ENABLED | ACTOR_FLAG_FRIENDLY) @@ -318,6 +319,21 @@ void EnAni_PostLimbDraw(PlayState* play, s32 limbIndex, Gfx** dList, Vec3s* rot, if (limbIndex == 15) { Matrix_MultVec3f(&sMultVec, &this->actor.focus.pos); } + + if (CVarGetInteger("gHoliday.Visual.Hats", 0)) { + if (limbIndex == 15) { + OPEN_DISPS(play->state.gfxCtx); + Matrix_Push(); + Matrix_RotateZYX(1992, 0, 2656, MTXMODE_APPLY); + Matrix_Translate(972.973f, 40.541f, 0.0f, MTXMODE_APPLY); + Matrix_Scale(0.965f, 0.965f, 0.965f, MTXMODE_APPLY); + gDPSetEnvColor(POLY_OPA_DISP++, 255, 0, 255, 255); + gSPMatrix(POLY_OPA_DISP++, MATRIX_NEWMTX(play->state.gfxCtx), G_MTX_NOPUSH | G_MTX_LOAD | G_MTX_MODELVIEW); + gSPDisplayList(POLY_OPA_DISP++, gPaperCrownGenericDL); + Matrix_Pop(); + CLOSE_DISPS(play->state.gfxCtx); + } + } } void EnAni_Draw(Actor* thisx, PlayState* play) { diff --git a/soh/src/overlays/actors/ovl_En_Arrow/z_en_arrow.c b/soh/src/overlays/actors/ovl_En_Arrow/z_en_arrow.c index f3551a113b4..076723d45fe 100644 --- a/soh/src/overlays/actors/ovl_En_Arrow/z_en_arrow.c +++ b/soh/src/overlays/actors/ovl_En_Arrow/z_en_arrow.c @@ -8,6 +8,9 @@ #include "objects/gameplay_keep/gameplay_keep.h" #include "objects/object_gi_nuts/object_gi_nuts.h" +#include "soh/Enhancements/Holiday/Archez.h" +#include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h" + #define FLAGS (ACTOR_FLAG_UPDATE_CULLING_DISABLED | ACTOR_FLAG_DRAW_CULLING_DISABLED) void EnArrow_Init(Actor* thisx, PlayState* play); @@ -227,6 +230,7 @@ void EnArrow_Shoot(EnArrow* this, PlayState* play) { case ARROW_FIRE: case ARROW_ICE: case ARROW_LIGHT: + GameInteractor_Should(VB_EN_ARROW_MAGIC_CONSUMPTION, true, this); Player_PlaySfx(&player->actor, NA_SE_IT_MAGIC_ARROW_SHOT); break; } @@ -508,6 +512,7 @@ void EnArrow_Draw(Actor* thisx, PlayState* play) { if (this->actor.params <= ARROW_0E) { Gfx_SetupDL_25Opa(play->state.gfxCtx); + SkipOverrideNextSkeleton(); SkelAnime_DrawLod(play, this->skelAnime.skeleton, this->skelAnime.jointTable, NULL, NULL, this, (this->actor.projectedPos.z < MREG(95)) ? 0 : 1); } else if (this->actor.speedXZ != 0.0f) { diff --git a/soh/src/overlays/actors/ovl_En_Bb/z_en_bb.c b/soh/src/overlays/actors/ovl_En_Bb/z_en_bb.c index 424f94a1c2d..764042be9ff 100644 --- a/soh/src/overlays/actors/ovl_En_Bb/z_en_bb.c +++ b/soh/src/overlays/actors/ovl_En_Bb/z_en_bb.c @@ -9,6 +9,7 @@ #include "objects/object_Bb/object_Bb.h" #include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h" #include "soh/ResourceManagerHelpers.h" +#include "soh_assets.h" #define FLAGS \ (ACTOR_FLAG_ATTENTION_ENABLED | ACTOR_FLAG_HOSTILE | ACTOR_FLAG_UPDATE_CULLING_DISABLED | \ @@ -1272,6 +1273,20 @@ void EnBb_PostLimbDraw(PlayState* play, s32 limbIndex, Gfx** dList, Vec3s* rot, EnBb* this = (EnBb*)thisx; BodyBreak_SetInfo(&this->bodyBreak, limbIndex, 4, 15, 15, dList, BODYBREAK_OBJECT_DEFAULT); + + if (CVarGetInteger("gHoliday.Visual.Hats", 0)) { + if (limbIndex == 15) { + OPEN_DISPS(play->state.gfxCtx); + Matrix_Push(); + Matrix_RotateZYX(-13063, 0, -27454, MTXMODE_APPLY); + Matrix_Translate(418.919f, -81.081f, 0.0f, MTXMODE_APPLY); + Matrix_Scale(1.757f, 1.757f, 1.757f, MTXMODE_APPLY); + gSPMatrix(POLY_OPA_DISP++, MATRIX_NEWMTX(play->state.gfxCtx), G_MTX_NOPUSH | G_MTX_LOAD | G_MTX_MODELVIEW); + gSPDisplayList(POLY_OPA_DISP++, gSantaHatGenericDL); + Matrix_Pop(); + CLOSE_DISPS(play->state.gfxCtx); + } + } } static Vec3f sFireIceOffsets[] = { diff --git a/soh/src/overlays/actors/ovl_En_Bigokuta/z_en_bigokuta.c b/soh/src/overlays/actors/ovl_En_Bigokuta/z_en_bigokuta.c index 368bf865b25..865498ee7a0 100644 --- a/soh/src/overlays/actors/ovl_En_Bigokuta/z_en_bigokuta.c +++ b/soh/src/overlays/actors/ovl_En_Bigokuta/z_en_bigokuta.c @@ -3,6 +3,8 @@ #include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h" #include "soh/ResourceManagerHelpers.h" +#include "soh/Enhancements/Holiday/Archez.h" + #define FLAGS \ (ACTOR_FLAG_ATTENTION_ENABLED | ACTOR_FLAG_HOSTILE | ACTOR_FLAG_UPDATE_CULLING_DISABLED | \ ACTOR_FLAG_DRAW_CULLING_DISABLED) @@ -869,6 +871,11 @@ s32 EnBigokuta_OverrideLimbDraw(PlayState* play, s32 limbIndex, Gfx** dList, Vec Matrix_Scale(1.0f, 1.25f - temp_f0, 1.25f - temp_f0, MTXMODE_APPLY); } } + + if (limbIndex == 15 || limbIndex == 16 || limbIndex == CVarGetInteger("gMyThing", 0)) { + SkipOverrideNextLimb(); + } + return false; } diff --git a/soh/src/overlays/actors/ovl_En_Bom/z_en_bom.c b/soh/src/overlays/actors/ovl_En_Bom/z_en_bom.c index 9fe450695e0..8479f79dfff 100644 --- a/soh/src/overlays/actors/ovl_En_Bom/z_en_bom.c +++ b/soh/src/overlays/actors/ovl_En_Bom/z_en_bom.c @@ -287,7 +287,12 @@ void EnBom_Update(Actor* thisx, PlayState* play2) { // spawn spark effect on even frames effPos = thisx->world.pos; - effPos.y += 17.0f; + if (CVarGetInteger("gHoliday.Gameplay.BombArrows.Active", 0) && thisx->parent && + thisx->parent->id == ACTOR_EN_ARROW) { + effPos.y += 5.0f; + } else { + effPos.y += 17.0f; + } if ((play->gameplayFrames % 2) == 0) { EffectSsGSpk_SpawnFuse(play, thisx, &effPos, &effVelocity, &effAccel); } diff --git a/soh/src/overlays/actors/ovl_En_Bom_Chu/z_en_bom_chu.c b/soh/src/overlays/actors/ovl_En_Bom_Chu/z_en_bom_chu.c index 40986590734..8e8c1b8fab4 100644 --- a/soh/src/overlays/actors/ovl_En_Bom_Chu/z_en_bom_chu.c +++ b/soh/src/overlays/actors/ovl_En_Bom_Chu/z_en_bom_chu.c @@ -1,6 +1,7 @@ #include "z_en_bom_chu.h" #include "overlays/actors/ovl_En_Bom/z_en_bom.h" #include "objects/gameplay_keep/gameplay_keep.h" +#include "soh_assets.h" #define FLAGS ACTOR_FLAG_UPDATE_CULLING_DISABLED @@ -527,5 +528,15 @@ void EnBomChu_Draw(Actor* thisx, PlayState* play) { gSPMatrix(POLY_OPA_DISP++, MATRIX_NEWMTX(play->state.gfxCtx), G_MTX_NOPUSH | G_MTX_LOAD | G_MTX_MODELVIEW); gSPDisplayList(POLY_OPA_DISP++, gBombchuDL); + if (CVarGetInteger("gHoliday.Visual.Hats", 0)) { + Matrix_Push(); + Matrix_RotateZYX(0, -3100, 17047, MTXMODE_APPLY); + Matrix_Translate(445.946f, -27.027f, 608.108f, MTXMODE_APPLY); + Matrix_Scale(0.541f, 0.541f, 0.541f, MTXMODE_APPLY); + gSPMatrix(POLY_OPA_DISP++, MATRIX_NEWMTX(play->state.gfxCtx), G_MTX_NOPUSH | G_MTX_LOAD | G_MTX_MODELVIEW); + gSPDisplayList(POLY_OPA_DISP++, gSantaHatGenericDL); + Matrix_Pop(); + } + CLOSE_DISPS(play->state.gfxCtx); } diff --git a/soh/src/overlays/actors/ovl_En_Box/z_en_box.c b/soh/src/overlays/actors/ovl_En_Box/z_en_box.c index 92ad93fb186..6dfd0762a69 100644 --- a/soh/src/overlays/actors/ovl_En_Box/z_en_box.c +++ b/soh/src/overlays/actors/ovl_En_Box/z_en_box.c @@ -674,7 +674,7 @@ void EnBox_UpdateSizeAndTexture(EnBox* this, PlayState* play) { } } - if (CVarGetInteger(CVAR_GENERAL("LetItSnow"), 0) && hasChristmasChestTexturesAvailable && + if (CVarGetInteger("gHoliday.Visual.PresentChests", 0) && hasChristmasChestTexturesAvailable && hasCreatedRandoChestTextures && !hasCustomChestDLs) { if (this->dyna.actor.scale.x == 0.01f) { this->boxBodyDL = gChristmasRedTreasureChestChestFrontDL; diff --git a/soh/src/overlays/actors/ovl_En_ChristmasDeco/z_en_christmasdeco.c b/soh/src/overlays/actors/ovl_En_ChristmasDeco/z_en_christmasdeco.c new file mode 100644 index 00000000000..1fde7bed426 --- /dev/null +++ b/soh/src/overlays/actors/ovl_En_ChristmasDeco/z_en_christmasdeco.c @@ -0,0 +1,65 @@ +/* + * File: z_en_christmasdeco.c + * Overlay: ovl_En_ChristmasDeco + * Description: Custom Christmas Decorations + */ + +#include "z_en_christmasdeco.h" +#include "soh_assets.h" + +#define CVAR(v) "gHoliday.Visual." v + +void EnChristmasDeco_Init(Actor* thisx, PlayState* play); +void EnChristmasDeco_Destroy(Actor* thisx, PlayState* play); +void EnChristmasDeco_Update(Actor* thisx, PlayState* play); +void EnChristmasDeco_Draw(Actor* thisx, PlayState* play); + +void EnChristmasDeco_Init(Actor* thisx, PlayState* play) { + if (play->sceneNum == SCENE_TEMPLE_OF_TIME) { + EnChristmasDeco* this = (EnChristmasDeco*)thisx; + this->actor.room = -1; + } +} + +void EnChristmasDeco_Destroy(Actor* thisx, PlayState* play) { +} + +void EnChristmasDeco_Update(Actor* thisx, PlayState* play) { +} + +void EnChristmasDeco_Draw(Actor* thisx, PlayState* play) { + if (!CVarGetInteger(CVAR("SnowingWeather"), 0)) { + return; + } + + float decoSize = 10.0f; + + OPEN_DISPS(play->state.gfxCtx); + + Gfx_SetupDL_25Opa(play->state.gfxCtx); + + Matrix_Scale(decoSize, decoSize, decoSize, MTXMODE_APPLY); + + gSPMatrix(POLY_OPA_DISP++, Matrix_NewMtx(play->state.gfxCtx, (char*)__FILE__, __LINE__), + G_MTX_MODELVIEW | G_MTX_LOAD); + + if (play->sceneNum == SCENE_KAKARIKO_VILLAGE) { + gSPDisplayList(POLY_OPA_DISP++, (Gfx*)gKakarikoDecorDL); + if (LINK_IS_CHILD) { + gSPDisplayList(POLY_OPA_DISP++, (Gfx*)gKakarikoChildDecorDL); + } else { + gSPDisplayList(POLY_OPA_DISP++, (Gfx*)gKakarikoAdultDecorDL); + } + } + + if (play->sceneNum == SCENE_TEMPLE_OF_TIME) { + gSPDisplayList(POLY_OPA_DISP++, (Gfx*)gTempleOfTimeDecorDL); + if (LINK_IS_CHILD) { + gSPDisplayList(POLY_OPA_DISP++, (Gfx*)gTempleOfTimeDecorDL); + } else { + gSPDisplayList(POLY_OPA_DISP++, (Gfx*)gTempleOfTimeDecorDL); + } + } + + CLOSE_DISPS(play->state.gfxCtx); +} diff --git a/soh/src/overlays/actors/ovl_En_ChristmasDeco/z_en_christmasdeco.h b/soh/src/overlays/actors/ovl_En_ChristmasDeco/z_en_christmasdeco.h new file mode 100644 index 00000000000..f2a317648c2 --- /dev/null +++ b/soh/src/overlays/actors/ovl_En_ChristmasDeco/z_en_christmasdeco.h @@ -0,0 +1,27 @@ +#ifndef Z_EN_CHRISTMASDECO_H +#define Z_EN_CHRISTMASDECO_H + +#include +#include "global.h" + +struct EnChristmasDeco; + +typedef void (*EnChristmasDecoActionFunc)(struct EnChristmasDeco*, PlayState*); + +typedef struct EnChristmasDeco { + Actor actor; + EnChristmasDecoActionFunc actionFunc; +} EnChristmasDeco; + +#ifdef __cplusplus +extern "C" { +#endif +void EnChristmasDeco_Init(Actor* thisx, PlayState* play); +void EnChristmasDeco_Destroy(Actor* thisx, PlayState* play); +void EnChristmasDeco_Update(Actor* thisx, PlayState* play); +void EnChristmasDeco_Draw(Actor* thisx, PlayState* play); +#ifdef __cplusplus +} +#endif + +#endif diff --git a/soh/src/overlays/actors/ovl_En_ChristmasTree/z_en_christmastree.c b/soh/src/overlays/actors/ovl_En_ChristmasTree/z_en_christmastree.c new file mode 100644 index 00000000000..70eebf7e14a --- /dev/null +++ b/soh/src/overlays/actors/ovl_En_ChristmasTree/z_en_christmastree.c @@ -0,0 +1,215 @@ +/* + * File: z_en_christmastree.c + * Overlay: ovl_En_ChristmasTree + * Description: Custom Christmas Tree for Ornament Hunt + */ + +#include "z_en_christmastree.h" +#include "soh_assets.h" +#include "soh/Enhancements/game-interactor/GameInteractor.h" +#include "soh/OTRGlobals.h" + +void EnChristmasTree_Init(Actor* thisx, PlayState* play); +void EnChristmasTree_Destroy(Actor* thisx, PlayState* play); +void EnChristmasTree_Update(Actor* thisx, PlayState* play); +void EnChristmasTree_Draw(Actor* thisx, PlayState* play); + +void EnChristmasTree_Wait(EnChristmasTree* this, PlayState* play); +void EnChristmasTree_Talk(EnChristmasTree* this, PlayState* play); +void EnChristmasTree_SetupEndTitle(EnChristmasTree* this, PlayState* play); +void EnChristmasTree_HandleEndTitle(EnChristmasTree* this, PlayState* play); + +static ColliderCylinderInit sCylinderInit = { + { + COLTYPE_NONE, + AT_NONE, + AC_NONE, + OC1_ON | OC1_TYPE_ALL, + OC2_TYPE_2, + COLSHAPE_CYLINDER, + }, + { + ELEMTYPE_UNK0, + { 0x00000000, 0x00, 0x00 }, + { 0x00000000, 0x00, 0x00 }, + TOUCH_NONE, + BUMP_NONE, + OCELEM_ON, + }, + { 100, 330, 0, { 0, 0, 0 } }, +}; + +static CollisionCheckInfoInit2 sColChkInfoInit = { 0, 0, 0, 0, MASS_IMMOVABLE }; + +void EnChristmasTree_Init(Actor* thisx, PlayState* play) { + EnChristmasTree* this = (EnChristmasTree*)thisx; + + ActorShape_Init(&this->actor.shape, 0.0f, ActorShadow_DrawCircle, 160.0f); + Collider_InitCylinder(play, &this->collider); + Collider_SetCylinder(play, &this->collider, &this->actor, &sCylinderInit); + CollisionCheck_SetInfo2(&this->actor.colChkInfo, NULL, &sColChkInfoInit); + Actor_UpdateBgCheckInfo(play, &this->actor, 0.0f, 0.0f, 0.0f, 4); + + this->actor.targetMode = 1; + this->actor.textId = 0x406B; // Hijacking bean seller text ID so I'm sure it doesn't clash + + this->actor.shape.rot.y = -14784; + + this->actionFunc = EnChristmasTree_Wait; +} + +void EnChristmasTree_Destroy(Actor* thisx, PlayState* play) { + EnChristmasTree* this = (EnChristmasTree*)thisx; + + Collider_DestroyCylinder(play, &this->collider); +} + +void EnChristmasTree_Wait(EnChristmasTree* this, PlayState* play) { + if (Actor_ProcessTalkRequest(&this->actor, play)) { // if talk is initiated + this->actionFunc = EnChristmasTree_Talk; + } else if ((this->actor.xzDistToPlayer < 170.0f) && Randomizer_GetSettingValue(RSK_TRIFORCE_HUNT)) { // talk range + func_8002F2CC(&this->actor, play, 170.0f); + } +} + +void EnChristmasTree_Talk(EnChristmasTree* this, PlayState* play) { + u8 dialogState = Message_GetState(&play->msgCtx); + if (dialogState != TEXT_STATE_CHOICE) { + if ((dialogState == TEXT_STATE_DONE) && Message_ShouldAdvance(play)) { // advanced final textbox + // Teleport to credits when goal is reached. + if (gSaveContext.ship.quest.data.randomizer.triforcePiecesCollected >= + Randomizer_GetSettingValue(RSK_TRIFORCE_HUNT_PIECES_REQUIRED)) { + gSaveContext.ship.stats.itemTimestamp[TIMESTAMP_TRIFORCE_COMPLETED] = GAMEPLAYSTAT_TOTAL_TIME; + gSaveContext.ship.stats.gameComplete = 1; + Play_PerformSave(play); + GameInteractor_SetTriforceHuntCreditsWarpActive(true); + this->actionFunc = EnChristmasTree_SetupEndTitle; + } else { + this->actionFunc = EnChristmasTree_Wait; + } + } + } +} + +void EnChristmasTree_SetupEndTitle(EnChristmasTree* this, PlayState* play) { + Player* player = GET_PLAYER(play); + + Actor_Spawn(&play->actorCtx, play, ACTOR_END_TITLE, 0, 0, 0, 0, 0, 0, 2, false); + + player->stateFlags1 = PLAYER_STATE1_INPUT_DISABLED; + + Flags_SetRandomizerInf(RAND_INF_GRANT_GANONS_BOSSKEY); + + Play_PerformSave(play); + + this->actionFunc = EnChristmasTree_HandleEndTitle; +} + +void EnChristmasTree_HandleEndTitle(EnChristmasTree* this, PlayState* play) { + Camera* camera = Play_GetCamera(play, play->mainCamera.thisIdx); + Player* player = GET_PLAYER(play); + Vec3f camAt; + Vec3f camEye; + + // Not forcing camera mode makes the camera jitter for a bit after setting position. + // Also forces letterbox bars. + Camera_ChangeMode(camera, CAM_MODE_STILL); + + // Christmas Tree's position + camAt.x = -734.0f; + camAt.y = 130.0f; + camAt.z = 420.0f; + + // Camera's position + camEye.x = -1237.0f; + camEye.y = 218.0f; + camEye.z = 408.0f; + + // Not setting fov manually makes camera zoom in after setting the above for a little bit. + camera->fov = 60.0f; + + // Set camera + Play_CameraSetAtEye(play, play->mainCamera.thisIdx, &camAt, &camEye); + + // Hide player so he's not visible in the final screen. Also move him so target arrow on tree dissapears. + player->actor.scale.x = player->actor.scale.y = player->actor.scale.z = 0.00001f; + player->actor.world.pos.z = 500.0f; + + // Hide HUD + Interface_ChangeAlpha(1); +} + +void EnChristmasTree_Update(Actor* thisx, PlayState* play) { + EnChristmasTree* this = (EnChristmasTree*)thisx; + ColliderCylinder* collider = &this->collider; + + Collider_UpdateCylinder(thisx, collider); + CollisionCheck_SetOC(play, &play->colChkCtx, (Collider*)collider); + + Actor_SetFocus(&this->actor, 80.0f); + + uint8_t triforceHuntActive = Randomizer_GetSettingValue(RSK_TRIFORCE_HUNT); + float percentageCompleted = (float)gSaveContext.ship.quest.data.randomizer.triforcePiecesCollected / + (float)Randomizer_GetSettingValue(RSK_TRIFORCE_HUNT_PIECES_REQUIRED); + + if ((percentageCompleted >= 1.0f || !triforceHuntActive) && !this->spawnedRupee) { + Actor_Spawn(&play->actorCtx, play, ACTOR_EN_WONDER_ITEM, this->actor.world.pos.x, this->actor.world.pos.y + 280, + this->actor.world.pos.z, 0, 0, LINK_IS_ADULT ? 1 : 4, 0x1ABF, false); + this->spawnedRupee = 1; + } + + this->actionFunc(this, play); +} + +void EnChristmasTree_Draw(Actor* thisx, PlayState* play) { + EnChristmasTree* this = (EnChristmasTree*)thisx; + + float treeSize = 55.0f; + uint8_t triforceHuntActive = Randomizer_GetSettingValue(RSK_TRIFORCE_HUNT); + float percentageCompleted = (float)gSaveContext.ship.quest.data.randomizer.triforcePiecesCollected / + (float)Randomizer_GetSettingValue(RSK_TRIFORCE_HUNT_PIECES_REQUIRED); + + OPEN_DISPS(play->state.gfxCtx); + + Gfx_SetupDL_25Opa(play->state.gfxCtx); + + Matrix_Scale(treeSize, treeSize, treeSize, MTXMODE_APPLY); + gSPMatrix(POLY_OPA_DISP++, Matrix_NewMtx(play->state.gfxCtx, (char*)__FILE__, __LINE__), + G_MTX_MODELVIEW | G_MTX_LOAD); + + gSPDisplayList(POLY_OPA_DISP++, (Gfx*)gXmasTreeDL); + + if (percentageCompleted >= 0.1f || !triforceHuntActive) { + gSPDisplayList(POLY_OPA_DISP++, (Gfx*)gXmasDecor10DL); + } + if (percentageCompleted >= 0.2f || !triforceHuntActive) { + gSPDisplayList(POLY_OPA_DISP++, (Gfx*)gXmasDecor20DL); + } + if (percentageCompleted >= 0.3f || !triforceHuntActive) { + gSPDisplayList(POLY_OPA_DISP++, (Gfx*)gXmasDecor30DL); + } + if (percentageCompleted >= 0.4f || !triforceHuntActive) { + gSPDisplayList(POLY_OPA_DISP++, (Gfx*)gXmasDecor40DL); + } + if (percentageCompleted >= 0.5f || !triforceHuntActive) { + gSPDisplayList(POLY_OPA_DISP++, (Gfx*)gXmasDecor50DL); + } + if (percentageCompleted >= 0.6f || !triforceHuntActive) { + gSPDisplayList(POLY_OPA_DISP++, (Gfx*)gXmasDecor60DL); + } + if (percentageCompleted >= 0.7f || !triforceHuntActive) { + gSPDisplayList(POLY_OPA_DISP++, (Gfx*)gXmasDecor70DL); + } + if (percentageCompleted >= 0.8f || !triforceHuntActive) { + gSPDisplayList(POLY_OPA_DISP++, (Gfx*)gXmasDecor80DL); + } + if (percentageCompleted >= 0.9f || !triforceHuntActive) { + gSPDisplayList(POLY_OPA_DISP++, (Gfx*)gXmasDecor90DL); + } + if (percentageCompleted >= 1.0f || !triforceHuntActive) { + gSPDisplayList(POLY_OPA_DISP++, (Gfx*)gXmasDecor100DL); + gSPDisplayList(POLY_OPA_DISP++, (Gfx*)gXmasStarDL); + } + + CLOSE_DISPS(play->state.gfxCtx); +} diff --git a/soh/src/overlays/actors/ovl_En_ChristmasTree/z_en_christmastree.h b/soh/src/overlays/actors/ovl_En_ChristmasTree/z_en_christmastree.h new file mode 100644 index 00000000000..fd580ac7167 --- /dev/null +++ b/soh/src/overlays/actors/ovl_En_ChristmasTree/z_en_christmastree.h @@ -0,0 +1,29 @@ +#ifndef Z_EN_CHRISTMASTREE_H +#define Z_EN_CHRISTMASTREE_H + +#include +#include "global.h" + +struct EnChristmasTree; + +typedef void (*EnChristmasTreeActionFunc)(struct EnChristmasTree*, PlayState*); + +typedef struct EnChristmasTree { + Actor actor; + ColliderCylinder collider; + EnChristmasTreeActionFunc actionFunc; + u8 spawnedRupee; +} EnChristmasTree; + +#ifdef __cplusplus +extern "C" { +#endif +void EnChristmasTree_Init(Actor* thisx, PlayState* play); +void EnChristmasTree_Destroy(Actor* thisx, PlayState* play); +void EnChristmasTree_Update(Actor* thisx, PlayState* play); +void EnChristmasTree_Draw(Actor* thisx, PlayState* play); +#ifdef __cplusplus +} +#endif + +#endif diff --git a/soh/src/overlays/actors/ovl_En_Clear_Tag/z_en_clear_tag.c b/soh/src/overlays/actors/ovl_En_Clear_Tag/z_en_clear_tag.c index 0b4f3bf0a55..442d5744b9d 100644 --- a/soh/src/overlays/actors/ovl_En_Clear_Tag/z_en_clear_tag.c +++ b/soh/src/overlays/actors/ovl_En_Clear_Tag/z_en_clear_tag.c @@ -244,15 +244,22 @@ void EnClearTag_Init(Actor* thisx, PlayState* play) { if (this->actor.params == CLEAR_TAG_LASER) { this->state = CLEAR_TAG_STATE_LASER; this->timers[CLEAR_TAG_TIMER_LASER_DEATH] = 70; - this->actor.speedXZ = 35.0f; - Actor_UpdateVelocityXYZ(&this->actor); - for (j = 0; j <= 0; j++) { - Actor_UpdatePos(&this->actor); + if (CVarGetInteger("gHoliday.AGreenSpoon.EvilGossipStone", 0)) { + this->actor.scale.x = 0.4f; + this->actor.scale.y = 0.4f; + this->actor.scale.z = 2.0f; + this->actor.speedXZ = MAX(10.0f, Actor_WorldDistXZToActor(thisx, &GET_PLAYER(gPlayState)->actor) * 0.33f); + } else { + this->actor.speedXZ = 35.0f; + Actor_UpdateVelocityXYZ(&this->actor); + for (j = 0; j <= 0; j++) { + Actor_UpdatePos(&this->actor); + } + this->actor.scale.x = 0.4f; + this->actor.scale.y = 0.4f; + this->actor.scale.z = 2.0f; + this->actor.speedXZ = 70.0f; } - this->actor.scale.x = 0.4f; - this->actor.scale.y = 0.4f; - this->actor.scale.z = 2.0f; - this->actor.speedXZ = 70.0f; this->actor.shape.rot.x = -this->actor.shape.rot.x; Actor_UpdateVelocityXYZ(&this->actor); @@ -572,12 +579,21 @@ void EnClearTag_Update(Actor* thisx, PlayState* play2) { } // Set laser collider properties. - this->collider.dim.radius = 23; - this->collider.dim.height = 25; - this->collider.dim.yShift = -10; - Collider_UpdateCylinder(&this->actor, &this->collider); - CollisionCheck_SetAT(play, &play->colChkCtx, &this->collider.base); - Actor_UpdateBgCheckInfo(play, &this->actor, 50.0f, 80.0f, 100.0f, 5); + if (CVarGetInteger("gHoliday.AGreenSpoon.EvilGossipStone", 0)) { + this->collider.dim.radius = 10; + this->collider.dim.height = 25; + this->collider.dim.yShift = -10; + Collider_UpdateCylinder(&this->actor, &this->collider); + CollisionCheck_SetAT(play, &play->colChkCtx, &this->collider.base); + Actor_UpdateBgCheckInfo(play, &this->actor, 10.0f, 10.0f, 10.0f, 5); + } else { + this->collider.dim.radius = 23; + this->collider.dim.height = 25; + this->collider.dim.yShift = -10; + Collider_UpdateCylinder(&this->actor, &this->collider); + CollisionCheck_SetAT(play, &play->colChkCtx, &this->collider.base); + Actor_UpdateBgCheckInfo(play, &this->actor, 50.0f, 80.0f, 100.0f, 5); + } // Check if the laser has hit a target, timed out, or hit the ground. if (this->actor.bgCheckFlags & 9 || hasAtHit || this->timers[CLEAR_TAG_TIMER_LASER_DEATH] == 0) { diff --git a/soh/src/overlays/actors/ovl_En_Cs/z_en_cs.c b/soh/src/overlays/actors/ovl_En_Cs/z_en_cs.c index 0c8c8e59eb3..331faa58972 100644 --- a/soh/src/overlays/actors/ovl_En_Cs/z_en_cs.c +++ b/soh/src/overlays/actors/ovl_En_Cs/z_en_cs.c @@ -2,6 +2,7 @@ #include "objects/object_cs/object_cs.h" #include "objects/object_link_child/object_link_child.h" #include "soh/ResourceManagerHelpers.h" +#include "soh_assets.h" #define FLAGS (ACTOR_FLAG_ATTENTION_ENABLED | ACTOR_FLAG_FRIENDLY) @@ -522,4 +523,19 @@ void EnCs_PostLimbDraw(PlayState* play, s32 limbIndex, Gfx** dList, Vec3s* rot, Matrix_RotateZ(5.0 * M_PI / 9.0, MTXMODE_APPLY); Matrix_Get(&this->spookyMaskMtx); } + + if (CVarGetInteger("gHoliday.Visual.Hats", 0)) { + if (limbIndex == 15) { + OPEN_DISPS(play->state.gfxCtx); + Matrix_Push(); + Matrix_RotateZYX(8191, -5757, -24133, MTXMODE_APPLY); + Matrix_Translate(270.27f, 297.297f, -513.514f, MTXMODE_APPLY); + Matrix_Scale(1.135f, 1.135f, 1.135f, MTXMODE_APPLY); + gDPSetEnvColor(POLY_OPA_DISP++, 255, 0, 0, 255); + gSPMatrix(POLY_OPA_DISP++, MATRIX_NEWMTX(play->state.gfxCtx), G_MTX_NOPUSH | G_MTX_LOAD | G_MTX_MODELVIEW); + gSPDisplayList(POLY_OPA_DISP++, gPaperCrownGenericDL); + Matrix_Pop(); + CLOSE_DISPS(play->state.gfxCtx); + } + } } diff --git a/soh/src/overlays/actors/ovl_En_Daiku_Kakariko/z_en_daiku_kakariko.c b/soh/src/overlays/actors/ovl_En_Daiku_Kakariko/z_en_daiku_kakariko.c index 9852620e0bc..33ad1c75720 100644 --- a/soh/src/overlays/actors/ovl_En_Daiku_Kakariko/z_en_daiku_kakariko.c +++ b/soh/src/overlays/actors/ovl_En_Daiku_Kakariko/z_en_daiku_kakariko.c @@ -7,6 +7,7 @@ #include "z_en_daiku_kakariko.h" #include "objects/object_daiku/object_daiku.h" #include "soh/ResourceManagerHelpers.h" +#include "soh_assets.h" #define FLAGS (ACTOR_FLAG_ATTENTION_ENABLED | ACTOR_FLAG_FRIENDLY | ACTOR_FLAG_UPDATE_CULLING_DISABLED) @@ -538,6 +539,55 @@ void EnDaikuKakariko_PostLimbDraw(PlayState* play, s32 limbIndex, Gfx** dList, V gSPDisplayList(POLY_OPA_DISP++, carpenterHeadDLists[this->actor.params & 3]); } + if (CVarGetInteger("gHoliday.Visual.Hats", 0)) { + if (limbIndex == 15) { + Matrix_Push(); + switch (this->actor.params) { + case 259: { + Matrix_RotateZYX(4649, 0, -3543, MTXMODE_APPLY); + Matrix_Translate(824.324f, 324.324f, -175.676f, MTXMODE_APPLY); + Matrix_Scale(0.966f, 0.966f, 0.966f, MTXMODE_APPLY); + gDPSetEnvColor(POLY_OPA_DISP++, 255, 0, 255, 255); + gSPMatrix(POLY_OPA_DISP++, MATRIX_NEWMTX(play->state.gfxCtx), + G_MTX_NOPUSH | G_MTX_LOAD | G_MTX_MODELVIEW); + gSPDisplayList(POLY_OPA_DISP++, gPaperCrownGenericDL); + break; + } + case 513: { + Matrix_RotateZYX(0, 0, -6200, MTXMODE_APPLY); + Matrix_Translate(770.27f, 567.568f, 0.0f, MTXMODE_APPLY); + Matrix_Scale(0.899f, 0.899f, 0.899f, MTXMODE_APPLY); + gDPSetEnvColor(POLY_OPA_DISP++, 0, 255, 255, 255); + gSPMatrix(POLY_OPA_DISP++, MATRIX_NEWMTX(play->state.gfxCtx), + G_MTX_NOPUSH | G_MTX_LOAD | G_MTX_MODELVIEW); + gSPDisplayList(POLY_OPA_DISP++, gPaperCrownGenericDL); + break; + } + case 2: { + Matrix_RotateZYX(0, 0, 7970, MTXMODE_APPLY); + Matrix_Translate(1270.27f, -878.378f, 0.0f, MTXMODE_APPLY); + Matrix_Scale(1.0f, 1.0f, 1.0f, MTXMODE_APPLY); + gDPSetEnvColor(POLY_OPA_DISP++, 0, 255, 0, 255); + gSPMatrix(POLY_OPA_DISP++, MATRIX_NEWMTX(play->state.gfxCtx), + G_MTX_NOPUSH | G_MTX_LOAD | G_MTX_MODELVIEW); + gSPDisplayList(POLY_OPA_DISP++, gPaperCrownGenericDL); + break; + } + case -256: + default: { + Matrix_RotateZYX(0, 0, -8635, MTXMODE_APPLY); + Matrix_Translate(675.676f, 716.216f, 0.0f, MTXMODE_APPLY); + Matrix_Scale(0.899f, 0.899f, 0.899f, MTXMODE_APPLY); + gDPSetEnvColor(POLY_OPA_DISP++, 255, 0, 0, 255); + gSPMatrix(POLY_OPA_DISP++, MATRIX_NEWMTX(play->state.gfxCtx), + G_MTX_NOPUSH | G_MTX_LOAD | G_MTX_MODELVIEW); + gSPDisplayList(POLY_OPA_DISP++, gPaperCrownGenericDL); + } + } + Matrix_Pop(); + } + } + CLOSE_DISPS(play->state.gfxCtx); } diff --git a/soh/src/overlays/actors/ovl_En_Dekubaba/z_en_dekubaba.c b/soh/src/overlays/actors/ovl_En_Dekubaba/z_en_dekubaba.c index b84103a4a0b..81af2e1b604 100644 --- a/soh/src/overlays/actors/ovl_En_Dekubaba/z_en_dekubaba.c +++ b/soh/src/overlays/actors/ovl_En_Dekubaba/z_en_dekubaba.c @@ -4,6 +4,7 @@ #include "overlays/effects/ovl_Effect_Ss_Hahen/z_eff_ss_hahen.h" #include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h" #include "soh/ResourceManagerHelpers.h" +#include "soh_assets.h" #define FLAGS (ACTOR_FLAG_ATTENTION_ENABLED | ACTOR_FLAG_HOSTILE) @@ -1276,6 +1277,20 @@ void EnDekubaba_PostLimbDraw(PlayState* play, s32 limbIndex, Gfx** dList, Vec3s* if (limbIndex == 1) { Collider_UpdateSpheres(limbIndex, &this->collider); } + + if (CVarGetInteger("gHoliday.Visual.Hats", 0)) { + if (limbIndex == 4) { + OPEN_DISPS(play->state.gfxCtx); + Matrix_Push(); + Matrix_RotateZYX(16485, -1425, -20964, MTXMODE_APPLY); + Matrix_Translate(-149.0f, 92.0f, -587.0f, MTXMODE_APPLY); + Matrix_Scale(1.534f, 1.534f, 1.534f, MTXMODE_APPLY); + gSPMatrix(POLY_OPA_DISP++, MATRIX_NEWMTX(play->state.gfxCtx), G_MTX_NOPUSH | G_MTX_LOAD | G_MTX_MODELVIEW); + gSPDisplayList(POLY_OPA_DISP++, gSantaHatGenericDL); + Matrix_Pop(); + CLOSE_DISPS(play->state.gfxCtx); + } + } } void EnDekubaba_Draw(Actor* thisx, PlayState* play) { diff --git a/soh/src/overlays/actors/ovl_En_Dns/z_en_dns.c b/soh/src/overlays/actors/ovl_En_Dns/z_en_dns.c index 4ba3520b19e..d9cea2e79b8 100644 --- a/soh/src/overlays/actors/ovl_En_Dns/z_en_dns.c +++ b/soh/src/overlays/actors/ovl_En_Dns/z_en_dns.c @@ -10,6 +10,7 @@ #include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h" #include "soh/OTRGlobals.h" #include "soh/ResourceManagerHelpers.h" +#include "soh_assets.h" #define FLAGS (ACTOR_FLAG_ATTENTION_ENABLED | ACTOR_FLAG_FRIENDLY) @@ -490,9 +491,27 @@ void EnDns_Update(Actor* thisx, PlayState* play) { } } +void EnDns_PostLimbDraw(PlayState* play, s32 limbIndex, Gfx** dList, Vec3s* rot, void* thisx) { + EnDns* this = (EnDns*)thisx; + + if (CVarGetInteger("gHoliday.Visual.Hats", 0)) { + if (limbIndex == 17) { + OPEN_DISPS(play->state.gfxCtx); + Matrix_Push(); + Matrix_RotateZYX(0, 0, 17490, MTXMODE_APPLY); + Matrix_Translate(4200.0f, -472.973f, 0.0f, MTXMODE_APPLY); + Matrix_Scale(4.932f, 4.932f, 4.932f, MTXMODE_APPLY); + gSPMatrix(POLY_OPA_DISP++, MATRIX_NEWMTX(play->state.gfxCtx), G_MTX_NOPUSH | G_MTX_LOAD | G_MTX_MODELVIEW); + gSPDisplayList(POLY_OPA_DISP++, gSantaHatGenericDL); + Matrix_Pop(); + CLOSE_DISPS(play->state.gfxCtx); + } + } +} + void EnDns_Draw(Actor* thisx, PlayState* play) { EnDns* this = (EnDns*)thisx; Gfx_SetupDL_25Opa(play->state.gfxCtx); - SkelAnime_DrawSkeletonOpa(play, &this->skelAnime, NULL, NULL, &this->actor); + SkelAnime_DrawSkeletonOpa(play, &this->skelAnime, NULL, EnDns_PostLimbDraw, &this->actor); } diff --git a/soh/src/overlays/actors/ovl_En_Dodongo/z_en_dodongo.c b/soh/src/overlays/actors/ovl_En_Dodongo/z_en_dodongo.c index 07a244cd3cc..5a9932be144 100644 --- a/soh/src/overlays/actors/ovl_En_Dodongo/z_en_dodongo.c +++ b/soh/src/overlays/actors/ovl_En_Dodongo/z_en_dodongo.c @@ -4,6 +4,7 @@ #include "objects/object_dodongo/object_dodongo.h" #include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h" #include "soh/ResourceManagerHelpers.h" +#include "soh_assets.h" #define FLAGS (ACTOR_FLAG_ATTENTION_ENABLED | ACTOR_FLAG_HOSTILE | ACTOR_FLAG_UPDATE_CULLING_DISABLED) @@ -914,6 +915,21 @@ void EnDodongo_PostLimbDraw(PlayState* play, s32 limbIndex, Gfx** dList, Vec3s* Matrix_MultVec3f(&baseOffset, &this->icePos[i]); } } + + if (CVarGetInteger("gHoliday.Visual.Hats", 0)) { + if (limbIndex == 7) { + OPEN_DISPS(play->state.gfxCtx); + Matrix_Push(); + Matrix_RotateZYX(0, 0, -13063, MTXMODE_APPLY); + Matrix_Translate(864.865f, 756.757f, 0.0f, MTXMODE_APPLY); + Matrix_Scale(0.662f, 0.662f, 0.662f, MTXMODE_APPLY); + gDPSetEnvColor(POLY_OPA_DISP++, 255, 0, 0, 255); + gSPMatrix(POLY_OPA_DISP++, MATRIX_NEWMTX(play->state.gfxCtx), G_MTX_NOPUSH | G_MTX_LOAD | G_MTX_MODELVIEW); + gSPDisplayList(POLY_OPA_DISP++, gPaperCrownGenericDL); + Matrix_Pop(); + CLOSE_DISPS(play->state.gfxCtx); + } + } } void EnDodongo_Draw(Actor* thisx, PlayState* play2) { diff --git a/soh/src/overlays/actors/ovl_En_Dog/z_en_dog.c b/soh/src/overlays/actors/ovl_En_Dog/z_en_dog.c index c8cb4bd971d..b7a0190a639 100644 --- a/soh/src/overlays/actors/ovl_En_Dog/z_en_dog.c +++ b/soh/src/overlays/actors/ovl_En_Dog/z_en_dog.c @@ -7,6 +7,7 @@ #include "z_en_dog.h" #include "objects/object_dog/object_dog.h" #include "soh/ResourceManagerHelpers.h" +#include "soh_assets.h" #define FLAGS 0 @@ -496,6 +497,19 @@ s32 EnDog_OverrideLimbDraw(PlayState* play, s32 limbIndex, Gfx** dList, Vec3f* p } void EnDog_PostLimbDraw(PlayState* play, s32 limbIndex, Gfx** dList, Vec3s* rot, void* thisx) { + if (CVarGetInteger("gHoliday.Visual.Hats", 0)) { + if (limbIndex == 4) { + OPEN_DISPS(play->state.gfxCtx); + Matrix_Push(); + Matrix_RotateZYX(20811, -32768, 3985, MTXMODE_APPLY); + Matrix_Translate(0.0f, 0.0f, 0.0f, MTXMODE_APPLY); + Matrix_Scale(1.25f, 1.25f, 1.25f, MTXMODE_APPLY); + gSPMatrix(POLY_OPA_DISP++, MATRIX_NEWMTX(play->state.gfxCtx), G_MTX_NOPUSH | G_MTX_LOAD | G_MTX_MODELVIEW); + gSPDisplayList(POLY_OPA_DISP++, gSantaHatGenericDL); + Matrix_Pop(); + CLOSE_DISPS(play->state.gfxCtx); + } + } } void EnDog_Draw(Actor* thisx, PlayState* play) { diff --git a/soh/src/overlays/actors/ovl_En_Door/z_en_door.c b/soh/src/overlays/actors/ovl_En_Door/z_en_door.c index 26835509464..a22ad9a6121 100644 --- a/soh/src/overlays/actors/ovl_En_Door/z_en_door.c +++ b/soh/src/overlays/actors/ovl_En_Door/z_en_door.c @@ -13,6 +13,8 @@ #include "soh/ResourceManagerHelpers.h" #include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h" +#include "soh/Enhancements/Holiday/Archez.h" + #define FLAGS ACTOR_FLAG_UPDATE_CULLING_DISABLED #define DOOR_AJAR_SLAM_RANGE 120.0f @@ -345,6 +347,7 @@ void EnDoor_Draw(Actor* thisx, PlayState* play) { OPEN_DISPS(play->state.gfxCtx); Gfx_SetupDL_25Opa(play->state.gfxCtx); + SkipOverrideNextSkeleton(); SkelAnime_DrawSkeletonOpa(play, &this->skelAnime, EnDoor_OverrideLimbDraw, NULL, &this->actor); if (this->actor.world.rot.y != 0) { if (this->actor.world.rot.y > 0) { diff --git a/soh/src/overlays/actors/ovl_En_Ds/z_en_ds.c b/soh/src/overlays/actors/ovl_En_Ds/z_en_ds.c index 1c5566d54e4..31980667c33 100644 --- a/soh/src/overlays/actors/ovl_En_Ds/z_en_ds.c +++ b/soh/src/overlays/actors/ovl_En_Ds/z_en_ds.c @@ -9,6 +9,7 @@ #include "soh/OTRGlobals.h" #include "soh/ResourceManagerHelpers.h" #include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h" +#include "soh_assets.h" #define FLAGS (ACTOR_FLAG_ATTENTION_ENABLED | ACTOR_FLAG_FRIENDLY) @@ -289,6 +290,21 @@ void EnDs_PostLimbDraw(PlayState* play, s32 limbIndex, Gfx** dList, Vec3s* rot, if (limbIndex == 5) { Matrix_MultVec3f(&sMultVec, &this->actor.focus.pos); } + + if (CVarGetInteger("gHoliday.Visual.Hats", 0)) { + if (limbIndex == 5) { + OPEN_DISPS(play->state.gfxCtx); + Matrix_Push(); + Matrix_RotateZYX(-1329, -3100, 0, MTXMODE_APPLY); + Matrix_Translate(1270.27f, 351.351f, -310.811f, MTXMODE_APPLY); + Matrix_Scale(0.797f, 0.797f, 0.797f, MTXMODE_APPLY); + gDPSetEnvColor(POLY_OPA_DISP++, 255, 0, 255, 255); + gSPMatrix(POLY_OPA_DISP++, MATRIX_NEWMTX(play->state.gfxCtx), G_MTX_NOPUSH | G_MTX_LOAD | G_MTX_MODELVIEW); + gSPDisplayList(POLY_OPA_DISP++, gPaperCrownGenericDL); + Matrix_Pop(); + CLOSE_DISPS(play->state.gfxCtx); + } + } } void EnDs_Draw(Actor* thisx, PlayState* play) { diff --git a/soh/src/overlays/actors/ovl_En_Du/z_en_du.c b/soh/src/overlays/actors/ovl_En_Du/z_en_du.c index 299d708693f..9bb1049cf22 100644 --- a/soh/src/overlays/actors/ovl_En_Du/z_en_du.c +++ b/soh/src/overlays/actors/ovl_En_Du/z_en_du.c @@ -3,6 +3,7 @@ #include "scenes/overworld/spot18/spot18_scene.h" #include "soh/OTRGlobals.h" #include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h" +#include "soh_assets.h" #define FLAGS (ACTOR_FLAG_ATTENTION_ENABLED | ACTOR_FLAG_FRIENDLY | ACTOR_FLAG_UPDATE_DURING_OCARINA) diff --git a/soh/src/overlays/actors/ovl_En_Elf/z_en_elf.c b/soh/src/overlays/actors/ovl_En_Elf/z_en_elf.c index 96a9297b969..e656ddcae78 100644 --- a/soh/src/overlays/actors/ovl_En_Elf/z_en_elf.c +++ b/soh/src/overlays/actors/ovl_En_Elf/z_en_elf.c @@ -9,6 +9,7 @@ #include #include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h" #include "soh/ResourceManagerHelpers.h" +#include "soh_assets.h" #define FLAGS (ACTOR_FLAG_UPDATE_CULLING_DISABLED | ACTOR_FLAG_DRAW_CULLING_DISABLED | ACTOR_FLAG_UPDATE_DURING_OCARINA) @@ -1497,6 +1498,26 @@ s32 EnElf_OverrideLimbDraw(PlayState* play, s32 limbIndex, Gfx** dList, Vec3f* p return false; } +s32 EnElf_PostLimbDraw(PlayState* play, s32 limbIndex, Gfx** dList, Vec3s* rot, void* thisx) { + EnElf* this = (EnElf*)thisx; + + if (CVarGetInteger("gHoliday.Visual.Hats", 0)) { + if (limbIndex == 2) { + OPEN_DISPS(play->state.gfxCtx); + Matrix_Push(); + Matrix_RotateZYX(0, 0, 17047, MTXMODE_APPLY); + Matrix_Translate(202.0f, 0.0f, 0.0f, MTXMODE_APPLY); + Matrix_Scale(0.595f, 0.595f, 0.595f, MTXMODE_APPLY); + gSPMatrix(POLY_OPA_DISP++, MATRIX_NEWMTX(play->state.gfxCtx), G_MTX_NOPUSH | G_MTX_LOAD | G_MTX_MODELVIEW); + gSPDisplayList(POLY_OPA_DISP++, gSantaHatGenericDL); + Matrix_Pop(); + CLOSE_DISPS(play->state.gfxCtx); + } + } + + return false; +} + void EnElf_Draw(Actor* thisx, PlayState* play) { s32 pad; f32 alphaScale; diff --git a/soh/src/overlays/actors/ovl_En_Fu/z_en_fu.c b/soh/src/overlays/actors/ovl_En_Fu/z_en_fu.c index 24c6ff5de0f..b4ad830dc91 100644 --- a/soh/src/overlays/actors/ovl_En_Fu/z_en_fu.c +++ b/soh/src/overlays/actors/ovl_En_Fu/z_en_fu.c @@ -9,6 +9,7 @@ #include "scenes/indoors/hakasitarelay/hakasitarelay_scene.h" #include "soh/OTRGlobals.h" #include "soh/ResourceManagerHelpers.h" +#include "soh_assets.h" #define FLAGS \ (ACTOR_FLAG_ATTENTION_ENABLED | ACTOR_FLAG_FRIENDLY | ACTOR_FLAG_UPDATE_CULLING_DISABLED | \ @@ -294,6 +295,20 @@ void EnFu_PostLimbDraw(PlayState* play, s32 limbIndex, Gfx** dList, Vec3s* rot, if (limbIndex == FU_LIMB_HEAD) { Matrix_MultVec3f(&sMtxSrc, &this->actor.focus.pos); } + + if (CVarGetInteger("gHoliday.Visual.Hats", 0)) { + if (limbIndex == 14) { + OPEN_DISPS(play->state.gfxCtx); + Matrix_Push(); + Matrix_RotateZYX(-27454, 0, 1992, MTXMODE_APPLY); + Matrix_Translate(878.378f, -108.108f, 67.568f, MTXMODE_APPLY); + Matrix_Scale(1.135f, 1.135f, 1.135f, MTXMODE_APPLY); + gSPMatrix(POLY_OPA_DISP++, MATRIX_NEWMTX(play->state.gfxCtx), G_MTX_NOPUSH | G_MTX_LOAD | G_MTX_MODELVIEW); + gSPDisplayList(POLY_OPA_DISP++, gSantaHatGenericDL); + Matrix_Pop(); + CLOSE_DISPS(play->state.gfxCtx); + } + } } void EnFu_Draw(Actor* thisx, PlayState* play) { diff --git a/soh/src/overlays/actors/ovl_En_Go/z_en_go.c b/soh/src/overlays/actors/ovl_En_Go/z_en_go.c index 3e306587e11..bf27a2afd6d 100644 --- a/soh/src/overlays/actors/ovl_En_Go/z_en_go.c +++ b/soh/src/overlays/actors/ovl_En_Go/z_en_go.c @@ -5,6 +5,9 @@ #include "soh/frame_interpolation.h" #include "soh/OTRGlobals.h" +#include "soh_assets.h" +#include "soh/Enhancements/Holiday/Archez.h" + #define FLAGS \ (ACTOR_FLAG_ATTENTION_ENABLED | ACTOR_FLAG_FRIENDLY | ACTOR_FLAG_UPDATE_CULLING_DISABLED | \ ACTOR_FLAG_DRAW_CULLING_DISABLED) @@ -1054,11 +1057,15 @@ void EnGo_DrawCurledUp(EnGo* this, PlayState* play) { OPEN_DISPS(play->state.gfxCtx); Matrix_Push(); - Gfx_SetupDL_25Opa(play->state.gfxCtx); - - gSPMatrix(POLY_OPA_DISP++, MATRIX_NEWMTX(play->state.gfxCtx), G_MTX_NOPUSH | G_MTX_LOAD | G_MTX_MODELVIEW); - - gSPDisplayList(POLY_OPA_DISP++, gGoronDL_00BD80); + if (CVarGetInteger("gHoliday.Archez.SnowGolems", 0)) { + Matrix_Translate(0.0f, 10.0f, 0.0f, MTXMODE_APPLY); + Matrix_Scale(1.75f, 1.75f, 1.75f, MTXMODE_APPLY); + gSPMatrix(POLY_OPA_DISP++, MATRIX_NEWMTX(play->state.gfxCtx), G_MTX_NOPUSH | G_MTX_LOAD | G_MTX_MODELVIEW); + gSPDisplayList(POLY_OPA_DISP++, gSnowballDL); + } else { + gSPMatrix(POLY_OPA_DISP++, MATRIX_NEWMTX(play->state.gfxCtx), G_MTX_NOPUSH | G_MTX_LOAD | G_MTX_MODELVIEW); + gSPDisplayList(POLY_OPA_DISP++, gGoronDL_00BD80); + } Matrix_MultVec3f(&D_80A41BB4, &this->actor.focus.pos); Matrix_Pop(); @@ -1075,8 +1082,15 @@ void EnGo_DrawRolling(EnGo* this, PlayState* play) { Gfx_SetupDL_25Opa(play->state.gfxCtx); Matrix_RotateZYX((s16)(play->state.frames * ((s16)this->actor.speedXZ * 1400)), 0, this->actor.shape.rot.z, MTXMODE_APPLY); - gSPMatrix(POLY_OPA_DISP++, MATRIX_NEWMTX(play->state.gfxCtx), G_MTX_NOPUSH | G_MTX_LOAD | G_MTX_MODELVIEW); - gSPDisplayList(POLY_OPA_DISP++, gGoronDL_00C140); + if (CVarGetInteger("gHoliday.Archez.SnowGolems", 0)) { + Matrix_Translate(0.0f, -10.0f, 0.0f, MTXMODE_APPLY); + Matrix_Scale(1.75f, 1.75f, 1.75f, MTXMODE_APPLY); + gSPMatrix(POLY_OPA_DISP++, MATRIX_NEWMTX(play->state.gfxCtx), G_MTX_NOPUSH | G_MTX_LOAD | G_MTX_MODELVIEW); + gSPDisplayList(POLY_OPA_DISP++, gSnowballDL); + } else { + gSPMatrix(POLY_OPA_DISP++, MATRIX_NEWMTX(play->state.gfxCtx), G_MTX_NOPUSH | G_MTX_LOAD | G_MTX_MODELVIEW); + gSPDisplayList(POLY_OPA_DISP++, gGoronDL_00C140); + } Matrix_MultVec3f(&D_80A41BC0, &this->actor.focus.pos); Matrix_Pop(); diff --git a/soh/src/overlays/actors/ovl_En_Go2/z_en_go2.c b/soh/src/overlays/actors/ovl_En_Go2/z_en_go2.c index 6b3d4602734..39b6aa40666 100644 --- a/soh/src/overlays/actors/ovl_En_Go2/z_en_go2.c +++ b/soh/src/overlays/actors/ovl_En_Go2/z_en_go2.c @@ -7,6 +7,10 @@ #include "soh/ResourceManagerHelpers.h" #include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h" +#include "soh/Enhancements/Holiday/Archez.h" +#include "soh_assets.h" +#include "functions.h" + #define FLAGS \ (ACTOR_FLAG_ATTENTION_ENABLED | ACTOR_FLAG_FRIENDLY | ACTOR_FLAG_UPDATE_CULLING_DISABLED | \ ACTOR_FLAG_DRAW_CULLING_DISABLED) @@ -2029,9 +2033,15 @@ s32 EnGo2_DrawCurledUp(EnGo2* this, PlayState* play) { Vec3f D_80A48554 = { 0.0f, 0.0f, 0.0f }; OPEN_DISPS(play->state.gfxCtx); - Gfx_SetupDL_25Opa(play->state.gfxCtx); - gSPMatrix(POLY_OPA_DISP++, MATRIX_NEWMTX(play->state.gfxCtx), G_MTX_NOPUSH | G_MTX_LOAD | G_MTX_MODELVIEW); - gSPDisplayList(POLY_OPA_DISP++, gGoronDL_00BD80); + if (CVarGetInteger("gHoliday.Archez.SnowGolems", 0)) { + Matrix_Translate(0.0f, 10.0f, 0.0f, MTXMODE_APPLY); + Matrix_Scale(1.75f, 1.75f, 1.75f, MTXMODE_APPLY); + gSPMatrix(POLY_OPA_DISP++, MATRIX_NEWMTX(play->state.gfxCtx), G_MTX_NOPUSH | G_MTX_LOAD | G_MTX_MODELVIEW); + gSPDisplayList(POLY_OPA_DISP++, gSnowballDL); + } else { + gSPMatrix(POLY_OPA_DISP++, MATRIX_NEWMTX(play->state.gfxCtx), G_MTX_NOPUSH | G_MTX_LOAD | G_MTX_MODELVIEW); + gSPDisplayList(POLY_OPA_DISP++, gGoronDL_00BD80); + } CLOSE_DISPS(play->state.gfxCtx); Matrix_MultVec3f(&D_80A48554, &this->actor.focus.pos); @@ -2047,8 +2057,15 @@ s32 EnGo2_DrawRolling(EnGo2* this, PlayState* play) { Gfx_SetupDL_25Opa(play->state.gfxCtx); speedXZ = this->actionFunc == EnGo2_ReverseRolling ? 0.0f : this->actor.speedXZ; Matrix_RotateZYX((play->state.frames * ((s16)speedXZ * 1400)), 0, this->actor.shape.rot.z, MTXMODE_APPLY); - gSPMatrix(POLY_OPA_DISP++, MATRIX_NEWMTX(play->state.gfxCtx), G_MTX_NOPUSH | G_MTX_LOAD | G_MTX_MODELVIEW); - gSPDisplayList(POLY_OPA_DISP++, gGoronDL_00C140); + if (CVarGetInteger("gHoliday.Archez.SnowGolems", 0)) { + Matrix_Translate(0.0f, -10.0f, 0.0f, MTXMODE_APPLY); + Matrix_Scale(1.75f, 1.75f, 1.75f, MTXMODE_APPLY); + gSPMatrix(POLY_OPA_DISP++, MATRIX_NEWMTX(play->state.gfxCtx), G_MTX_NOPUSH | G_MTX_LOAD | G_MTX_MODELVIEW); + gSPDisplayList(POLY_OPA_DISP++, gSnowballDL); + } else { + gSPMatrix(POLY_OPA_DISP++, MATRIX_NEWMTX(play->state.gfxCtx), G_MTX_NOPUSH | G_MTX_LOAD | G_MTX_MODELVIEW); + gSPDisplayList(POLY_OPA_DISP++, gGoronDL_00C140); + } CLOSE_DISPS(play->state.gfxCtx); Matrix_MultVec3f(&D_80A48560, &this->actor.focus.pos); return 1; diff --git a/soh/src/overlays/actors/ovl_En_Hata/z_en_hata.c b/soh/src/overlays/actors/ovl_En_Hata/z_en_hata.c index cec19bafe51..0b329768526 100644 --- a/soh/src/overlays/actors/ovl_En_Hata/z_en_hata.c +++ b/soh/src/overlays/actors/ovl_En_Hata/z_en_hata.c @@ -7,6 +7,8 @@ #include "z_en_hata.h" #include "objects/object_hata/object_hata.h" +#include "soh/Enhancements/Holiday/Archez.h" + #define FLAGS 0 void EnHata_Init(Actor* thisx, PlayState* play); @@ -130,6 +132,11 @@ s32 EnHata_OverrideLimbDraw(PlayState* play, s32 limbIndex, Gfx** dList, Vec3f* rot->y += limbs[limbIndex].y; rot->z += limbs[limbIndex].z; } + + if (limbIndex == FLAGPOLE_LIMB_POLE_BASE || limbIndex == FLAGPOLE_LIMB_POLE) { + SkipOverrideNextLimb(); + } + return false; } diff --git a/soh/src/overlays/actors/ovl_En_Heishi1/z_en_heishi1.c b/soh/src/overlays/actors/ovl_En_Heishi1/z_en_heishi1.c index 3ca58303350..5eb15a77567 100644 --- a/soh/src/overlays/actors/ovl_En_Heishi1/z_en_heishi1.c +++ b/soh/src/overlays/actors/ovl_En_Heishi1/z_en_heishi1.c @@ -9,6 +9,8 @@ #include "vt.h" #include "soh/ResourceManagerHelpers.h" +#include "soh/Enhancements/Holiday/Archez.h" + #define FLAGS ACTOR_FLAG_UPDATE_CULLING_DISABLED void EnHeishi1_Init(Actor* thisx, PlayState* play); @@ -495,6 +497,10 @@ s32 EnHeishi1_OverrideLimbDraw(PlayState* play, s32 limbIndex, Gfx** dList, Vec3 rot->x += (s16)this->headAngle; } + if (limbIndex == 15) { + SkipOverrideNextLimb(); + } + return false; } diff --git a/soh/src/overlays/actors/ovl_En_Heishi2/z_en_heishi2.c b/soh/src/overlays/actors/ovl_En_Heishi2/z_en_heishi2.c index 192d749d493..b3461ab9432 100644 --- a/soh/src/overlays/actors/ovl_En_Heishi2/z_en_heishi2.c +++ b/soh/src/overlays/actors/ovl_En_Heishi2/z_en_heishi2.c @@ -15,6 +15,8 @@ #include "soh/Enhancements/game-interactor/GameInteractor.h" #include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h" +#include "soh/Enhancements/Holiday/Archez.h" + #define FLAGS (ACTOR_FLAG_ATTENTION_ENABLED | ACTOR_FLAG_FRIENDLY) void EnHeishi2_Init(Actor* thisx, PlayState* play); @@ -832,6 +834,10 @@ s32 EnHeishi2_OverrideLimbDraw(PlayState* play, s32 limbIndex, Gfx** dList, Vec3 } } + if (limbIndex == 15) { + SkipOverrideNextLimb(); + } + return false; } diff --git a/soh/src/overlays/actors/ovl_En_Heishi3/z_en_heishi3.c b/soh/src/overlays/actors/ovl_En_Heishi3/z_en_heishi3.c index 9245050315b..52bac2511cd 100644 --- a/soh/src/overlays/actors/ovl_En_Heishi3/z_en_heishi3.c +++ b/soh/src/overlays/actors/ovl_En_Heishi3/z_en_heishi3.c @@ -9,6 +9,8 @@ #include "vt.h" #include "soh/ResourceManagerHelpers.h" +#include "soh/Enhancements/Holiday/Archez.h" + #define FLAGS 0 void EnHeishi3_Init(Actor* thisx, PlayState* play); @@ -243,6 +245,10 @@ s32 EnHeishi3_OverrideLimbDraw(PlayState* play, s32 limbIndex, Gfx** dList, Vec3 rot->z += this->unk_264; } + if (limbIndex == 15) { + SkipOverrideNextLimb(); + } + return false; } diff --git a/soh/src/overlays/actors/ovl_En_Heishi4/z_en_heishi4.c b/soh/src/overlays/actors/ovl_En_Heishi4/z_en_heishi4.c index 0bc1730596c..b24c430930b 100644 --- a/soh/src/overlays/actors/ovl_En_Heishi4/z_en_heishi4.c +++ b/soh/src/overlays/actors/ovl_En_Heishi4/z_en_heishi4.c @@ -3,6 +3,9 @@ #include "vt.h" #include "soh/OTRGlobals.h" #include "soh/ResourceManagerHelpers.h" +#include "soh_assets.h" + +#include "soh/Enhancements/Holiday/Archez.h" #define FLAGS (ACTOR_FLAG_ATTENTION_ENABLED | ACTOR_FLAG_FRIENDLY) @@ -414,6 +417,31 @@ s32 EnHeishi_OverrideLimbDraw(PlayState* play, s32 limbIndex, Gfx** dList, Vec3f rot->x += this->unk_260.y; rot->z += this->unk_260.z; } + + if (limbIndex == 15) { + SkipOverrideNextLimb(); + } + + return false; +} + +s32 EnHeishi4_PostLimbDraw(PlayState* play, s32 limbIndex, Gfx** dList, Vec3s* rot, void* thisx) { + EnHeishi4* this = (EnHeishi4*)thisx; + + if (CVarGetInteger("gHoliday.Visual.Hats", 0)) { + if (limbIndex == 16) { + OPEN_DISPS(play->state.gfxCtx); + Matrix_Push(); + Matrix_RotateZYX(0, 0, 442, MTXMODE_APPLY); + Matrix_Translate(256.757f, 121.621f, 0.0f, MTXMODE_APPLY); + Matrix_Scale(1.337f, 1.337f, 1.337f, MTXMODE_APPLY); + gSPMatrix(POLY_OPA_DISP++, MATRIX_NEWMTX(play->state.gfxCtx), G_MTX_NOPUSH | G_MTX_LOAD | G_MTX_MODELVIEW); + gSPDisplayList(POLY_OPA_DISP++, gSantaHatGenericDL); + Matrix_Pop(); + CLOSE_DISPS(play->state.gfxCtx); + } + } + return false; } @@ -421,5 +449,5 @@ void EnHeishi4_Draw(Actor* thisx, PlayState* play) { EnHeishi4* this = (EnHeishi4*)thisx; Gfx_SetupDL_25Opa(play->state.gfxCtx); - SkelAnime_DrawSkeletonOpa(play, &this->skelAnime, EnHeishi_OverrideLimbDraw, NULL, this); + SkelAnime_DrawSkeletonOpa(play, &this->skelAnime, EnHeishi_OverrideLimbDraw, EnHeishi4_PostLimbDraw, this); } diff --git a/soh/src/overlays/actors/ovl_En_Hs2/z_en_hs2.c b/soh/src/overlays/actors/ovl_En_Hs2/z_en_hs2.c index 8b745635077..4538c25b693 100644 --- a/soh/src/overlays/actors/ovl_En_Hs2/z_en_hs2.c +++ b/soh/src/overlays/actors/ovl_En_Hs2/z_en_hs2.c @@ -8,6 +8,7 @@ #include "vt.h" #include "objects/object_hs/object_hs.h" #include "soh/ResourceManagerHelpers.h" +#include "soh_assets.h" #define FLAGS (ACTOR_FLAG_ATTENTION_ENABLED | ACTOR_FLAG_FRIENDLY) @@ -161,6 +162,21 @@ void EnHs2_PostLimbDraw(PlayState* play, s32 limbIndex, Gfx** dList, Vec3s* rot, if (limbIndex == 9) { Matrix_MultVec3f(&D_80A6F4CC, &this->actor.focus.pos); } + + if (CVarGetInteger("gHoliday.Visual.Hats", 0)) { + if (limbIndex == 9) { + OPEN_DISPS(play->state.gfxCtx); + Matrix_Push(); + Matrix_RotateZYX(0, 0, -6421, MTXMODE_APPLY); + Matrix_Translate(621.622f, 378.378f, 0.0f, MTXMODE_APPLY); + Matrix_Scale(0.763f, 0.763f, 0.763f, MTXMODE_APPLY); + gDPSetEnvColor(POLY_OPA_DISP++, 0, 255, 255, 255); + gSPMatrix(POLY_OPA_DISP++, MATRIX_NEWMTX(play->state.gfxCtx), G_MTX_NOPUSH | G_MTX_LOAD | G_MTX_MODELVIEW); + gSPDisplayList(POLY_OPA_DISP++, gPaperCrownGenericDL); + Matrix_Pop(); + CLOSE_DISPS(play->state.gfxCtx); + } + } } void EnHs2_Draw(Actor* thisx, PlayState* play) { diff --git a/soh/src/overlays/actors/ovl_En_Hy/z_en_hy.c b/soh/src/overlays/actors/ovl_En_Hy/z_en_hy.c index 209137b0d27..9027163bd9d 100644 --- a/soh/src/overlays/actors/ovl_En_Hy/z_en_hy.c +++ b/soh/src/overlays/actors/ovl_En_Hy/z_en_hy.c @@ -17,6 +17,7 @@ #include "soh/OTRGlobals.h" #include "soh/ResourceManagerHelpers.h" #include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h" +#include "soh_assets.h" #define FLAGS (ACTOR_FLAG_ATTENTION_ENABLED | ACTOR_FLAG_FRIENDLY | ACTOR_FLAG_UPDATE_CULLING_DISABLED) @@ -553,7 +554,9 @@ u16 func_80A6F810(PlayState* play, Actor* thisx) { return 0x5058; } case ENHY_TYPE_BOB_18: - if (!LINK_IS_ADULT) { + if (CVarGetInteger("gHoliday.Fredomato.TreeChopper", 0)) { + return 0x505e; + } else if (!LINK_IS_ADULT) { return (Flags_GetEventChkInf(EVENTCHKINF_ZELDA_FLED_HYRULE_CASTLE)) ? 0x505F : ((Flags_GetInfTable(INFTABLE_163)) ? 0x505E : 0x505D); @@ -907,6 +910,21 @@ void EnHy_Init(Actor* thisx, PlayState* play) { Actor_Kill(&this->actor); } + if (play->sceneNum == SCENE_KAKARIKO_VILLAGE && CVarGetInteger(CVAR_GENERAL("LetItSnow"), 0)) { + if (this->actor.params == 1929) { + this->actor.world.pos.x = 261.826; + this->actor.world.pos.y = 240.0; + this->actor.world.pos.z = 1669.660; + this->actor.shape.rot.y = 23784; + } + if (this->actor.params == 1930) { + this->actor.world.pos.x = 262.224; + this->actor.world.pos.y = 240.0; + this->actor.world.pos.z = 1594.390; + this->actor.shape.rot.y = 7728; + } + } + this->getItemEntry = (GetItemEntry)GET_ITEM_NONE; this->actionFunc = EnHy_InitImpl; } @@ -1188,6 +1206,111 @@ void EnHy_PostLimbDraw(PlayState* play, s32 limbIndex, Gfx** dList, Vec3s* rot, Matrix_MultVec3f(&sp3C, &this->actor.focus.pos); } + if (CVarGetInteger("gHoliday.Visual.Hats", 0)) { + if (limbIndex == 15) { + Matrix_Push(); + switch (this->actor.params) { + case 1938: { + Matrix_RotateZYX(5313, 0, -1550, MTXMODE_APPLY); + Matrix_Translate(1108.108f, 54.054f, 0.0f, MTXMODE_APPLY); + Matrix_Scale(1.0f, 1.0f, 1.0f, MTXMODE_APPLY); + gSPMatrix(POLY_OPA_DISP++, MATRIX_NEWMTX(play->state.gfxCtx), + G_MTX_NOPUSH | G_MTX_LOAD | G_MTX_MODELVIEW); + gSPDisplayList(POLY_OPA_DISP++, gSantaHatGenericDL); + break; + } + case 135: + case 7: { + Matrix_RotateZYX(1328, 0, 885, MTXMODE_APPLY); + Matrix_Translate(864.865f, 229.73f, 0.0f, MTXMODE_APPLY); + Matrix_Scale(1.25f, 1.25f, 1.25f, MTXMODE_APPLY); + gSPMatrix(POLY_OPA_DISP++, MATRIX_NEWMTX(play->state.gfxCtx), + G_MTX_NOPUSH | G_MTX_LOAD | G_MTX_MODELVIEW); + gSPDisplayList(POLY_OPA_DISP++, gSantaHatGenericDL); + break; + } + case 1922: { + Matrix_RotateZYX(4206, 221, -3543, MTXMODE_APPLY); + Matrix_Translate(662.162f, 162.162f, -27.027f, MTXMODE_APPLY); + Matrix_Scale(1.0f, 1.0f, 1.0f, MTXMODE_APPLY); + gSPMatrix(POLY_OPA_DISP++, MATRIX_NEWMTX(play->state.gfxCtx), + G_MTX_NOPUSH | G_MTX_LOAD | G_MTX_MODELVIEW); + gSPDisplayList(POLY_OPA_DISP++, gSantaHatGenericDL); + break; + } + case 1925: { + Matrix_RotateZYX(-9521, 442, -5536, MTXMODE_APPLY); + Matrix_Translate(351.351f, 256.757f, 283.784f, MTXMODE_APPLY); + Matrix_Scale(1.217f, 1.217f, 1.217f, MTXMODE_APPLY); + gSPMatrix(POLY_OPA_DISP++, MATRIX_NEWMTX(play->state.gfxCtx), + G_MTX_NOPUSH | G_MTX_LOAD | G_MTX_MODELVIEW); + gSPDisplayList(POLY_OPA_DISP++, gSantaHatGenericDL); + break; + } + case 1920: { + Matrix_RotateZYX(0, 0, 3321, MTXMODE_APPLY); + Matrix_Translate(1148.649f, 0.0f, 0.0f, MTXMODE_APPLY); + Matrix_Scale(0.73f, 0.73f, 0.73f, MTXMODE_APPLY); + gDPSetEnvColor(POLY_OPA_DISP++, 255, 0, 255, 255); + gSPMatrix(POLY_OPA_DISP++, MATRIX_NEWMTX(play->state.gfxCtx), + G_MTX_NOPUSH | G_MTX_LOAD | G_MTX_MODELVIEW); + gSPDisplayList(POLY_OPA_DISP++, gPaperCrownGenericDL); + break; + } + case 1930: { + Matrix_RotateZYX(3542, 0, 0, MTXMODE_APPLY); + Matrix_Translate(972.973f, -13.514f, 54.054f, MTXMODE_APPLY); + Matrix_Scale(0.831f, 0.831f, 0.831f, MTXMODE_APPLY); + gDPSetEnvColor(POLY_OPA_DISP++, 255, 0, 0, 255); + gSPMatrix(POLY_OPA_DISP++, MATRIX_NEWMTX(play->state.gfxCtx), + G_MTX_NOPUSH | G_MTX_LOAD | G_MTX_MODELVIEW); + gSPDisplayList(POLY_OPA_DISP++, gPaperCrownGenericDL); + break; + } + case 1929: { + Matrix_RotateZYX(3542, 0, 0, MTXMODE_APPLY); + Matrix_Translate(972.973f, -13.514f, 54.054f, MTXMODE_APPLY); + Matrix_Scale(0.831f, 0.831f, 0.831f, MTXMODE_APPLY); + gDPSetEnvColor(POLY_OPA_DISP++, 0, 0, 255, 255); + gSPMatrix(POLY_OPA_DISP++, MATRIX_NEWMTX(play->state.gfxCtx), + G_MTX_NOPUSH | G_MTX_LOAD | G_MTX_MODELVIEW); + gSPDisplayList(POLY_OPA_DISP++, gPaperCrownGenericDL); + break; + } + case 1921: { + Matrix_RotateZYX(0, 0, 664, MTXMODE_APPLY); + Matrix_Translate(1256.757f, -297.297f, -40.541f, MTXMODE_APPLY); + Matrix_Scale(1.135f, 1.135f, 1.135f, MTXMODE_APPLY); + gDPSetEnvColor(POLY_OPA_DISP++, 0, 255, 0, 255); + gSPMatrix(POLY_OPA_DISP++, MATRIX_NEWMTX(play->state.gfxCtx), + G_MTX_NOPUSH | G_MTX_LOAD | G_MTX_MODELVIEW); + gSPDisplayList(POLY_OPA_DISP++, gPaperCrownGenericDL); + break; + } + case 1939: { + Matrix_RotateZYX(2656, 1328, 1992, MTXMODE_APPLY); + Matrix_Translate(1094.594f, 94.594f, 0.0f, MTXMODE_APPLY); + Matrix_Scale(1.351f, 1.351f, 1.351f, MTXMODE_APPLY); + gSPMatrix(POLY_OPA_DISP++, MATRIX_NEWMTX(play->state.gfxCtx), + G_MTX_NOPUSH | G_MTX_LOAD | G_MTX_MODELVIEW); + gSPDisplayList(POLY_OPA_DISP++, gSantaHatGenericDL); + break; + } + default: { + Matrix_RotateZYX(0, 0, 664, MTXMODE_APPLY); + Matrix_Translate(783.784f, 94.594f, 0.0f, MTXMODE_APPLY); + Matrix_Scale(0.662f, 0.662f, 0.662f, MTXMODE_APPLY); + gDPSetEnvColor(POLY_OPA_DISP++, 255, 255, 0, 255); + gSPMatrix(POLY_OPA_DISP++, MATRIX_NEWMTX(play->state.gfxCtx), + G_MTX_NOPUSH | G_MTX_LOAD | G_MTX_MODELVIEW); + gSPDisplayList(POLY_OPA_DISP++, gPaperCrownGenericDL); + break; + } + } + Matrix_Pop(); + } + } + CLOSE_DISPS(play->state.gfxCtx); } diff --git a/soh/src/overlays/actors/ovl_En_Ik/z_en_ik.c b/soh/src/overlays/actors/ovl_En_Ik/z_en_ik.c index c4dbe3f3af0..e12581ee4aa 100644 --- a/soh/src/overlays/actors/ovl_En_Ik/z_en_ik.c +++ b/soh/src/overlays/actors/ovl_En_Ik/z_en_ik.c @@ -10,6 +10,9 @@ #include "vt.h" #include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h" #include "soh/ResourceManagerHelpers.h" +#include "soh_assets.h" + +#include "soh/Enhancements/Holiday/Archez.h" #define FLAGS ACTOR_FLAG_UPDATE_CULLING_DISABLED @@ -850,6 +853,11 @@ s32 EnIk_OverrideLimbDraw3(PlayState* play, s32 limbIndex, Gfx** dList, Vec3f* p *dList = NULL; } } + + if (limbIndex == 17) { + SkipOverrideNextLimb(); + } + return false; } @@ -947,6 +955,19 @@ void EnIk_PostLimbDraw3(PlayState* play, s32 limbIndex, Gfx** dList, Vec3s* rot, break; } + if (CVarGetInteger("gHoliday.Visual.Hats", 0)) { + if (limbIndex == 11) { + Matrix_Push(); + Matrix_RotateZYX(0, 0, -15056, MTXMODE_APPLY); + Matrix_Translate(824.324f, 472.973f, 0.0f, MTXMODE_APPLY); + Matrix_Scale(0.845f, 0.845f, 0.845f, MTXMODE_APPLY); + gDPSetEnvColor(POLY_OPA_DISP++, 255, 100, 100, 255); + gSPMatrix(POLY_OPA_DISP++, MATRIX_NEWMTX(play->state.gfxCtx), G_MTX_NOPUSH | G_MTX_LOAD | G_MTX_MODELVIEW); + gSPDisplayList(POLY_OPA_DISP++, gPaperCrownGenericDL); + Matrix_Pop(); + } + } + CLOSE_DISPS(play->state.gfxCtx); } @@ -1181,6 +1202,10 @@ s32 EnIk_OverrideLimbDraw2(PlayState* play, s32 limbIndex, Gfx** dList, Vec3f* p } } + if (limbIndex == 17) { + SkipOverrideNextLimb(); + } + return 0; } @@ -1223,6 +1248,20 @@ void EnIk_PostLimbDraw2(PlayState* play, s32 limbIndex, Gfx** dList, Vec3s* rot, } } break; } + + if (CVarGetInteger("gHoliday.Visual.Hats", 0)) { + if (limbIndex == 11) { + Matrix_Push(); + Matrix_RotateZYX(0, 0, -15056, MTXMODE_APPLY); + Matrix_Translate(824.324f, 472.973f, 0.0f, MTXMODE_APPLY); + Matrix_Scale(0.845f, 0.845f, 0.845f, MTXMODE_APPLY); + gDPSetEnvColor(POLY_OPA_DISP++, 255, 100, 100, 255); + gSPMatrix(POLY_OPA_DISP++, MATRIX_NEWMTX(play->state.gfxCtx), G_MTX_NOPUSH | G_MTX_LOAD | G_MTX_MODELVIEW); + gSPDisplayList(POLY_OPA_DISP++, gPaperCrownGenericDL); + Matrix_Pop(); + } + } + CLOSE_DISPS(gfxCtx); } @@ -1328,6 +1367,7 @@ s32 EnIk_OverrideLimbDraw1(PlayState* play, s32 limbIndex, Gfx** dList, Vec3f* p if (curFrame < 120.0f) { *dList = NULL; } else { + SkipOverrideNextLimb(); func_80A76E2C(this, play, pos); } break; @@ -1368,6 +1408,19 @@ void EnIk_PostLimbDraw1(PlayState* play, s32 limbIndex, Gfx** dList, Vec3s* rot, break; } + if (CVarGetInteger("gHoliday.Visual.Hats", 0)) { + if (limbIndex == 11) { + Matrix_Push(); + Matrix_RotateZYX(0, 0, -15056, MTXMODE_APPLY); + Matrix_Translate(824.324f, 472.973f, 0.0f, MTXMODE_APPLY); + Matrix_Scale(0.845f, 0.845f, 0.845f, MTXMODE_APPLY); + gDPSetEnvColor(POLY_OPA_DISP++, 255, 100, 100, 255); + gSPMatrix(POLY_OPA_DISP++, MATRIX_NEWMTX(play->state.gfxCtx), G_MTX_NOPUSH | G_MTX_LOAD | G_MTX_MODELVIEW); + gSPDisplayList(POLY_OPA_DISP++, gPaperCrownGenericDL); + Matrix_Pop(); + } + } + CLOSE_DISPS(gfxCtx); } diff --git a/soh/src/overlays/actors/ovl_En_In/z_en_in.c b/soh/src/overlays/actors/ovl_En_In/z_en_in.c index 18903bfbb71..1bf64385424 100644 --- a/soh/src/overlays/actors/ovl_En_In/z_en_in.c +++ b/soh/src/overlays/actors/ovl_En_In/z_en_in.c @@ -2,6 +2,7 @@ #include "overlays/actors/ovl_En_Horse/z_en_horse.h" #include "objects/object_in/object_in.h" #include "soh/ResourceManagerHelpers.h" +#include "soh_assets.h" #define FLAGS (ACTOR_FLAG_ATTENTION_ENABLED | ACTOR_FLAG_FRIENDLY | ACTOR_FLAG_UPDATE_CULLING_DISABLED) @@ -992,6 +993,18 @@ void EnIn_PostLimbDraw(PlayState* play, s32 limbIndex, Gfx** dList, Vec3s* rot, gSPDisplayList(POLY_OPA_DISP++, gIngoChildEraPitchForkDL); } + if (CVarGetInteger("gHoliday.Visual.Hats", 0)) { + if (limbIndex == 16) { + Matrix_Push(); + Matrix_RotateZYX(-8192, -222, -11513, MTXMODE_APPLY); + Matrix_Translate(770.0f, 837.0f, 878.0f, MTXMODE_APPLY); + Matrix_Scale(1.068f, 1.068f, 1.068f, MTXMODE_APPLY); + gSPMatrix(POLY_OPA_DISP++, MATRIX_NEWMTX(play->state.gfxCtx), G_MTX_NOPUSH | G_MTX_LOAD | G_MTX_MODELVIEW); + gSPDisplayList(POLY_OPA_DISP++, gSantaHatGenericDL); + Matrix_Pop(); + } + } + CLOSE_DISPS(play->state.gfxCtx); } diff --git a/soh/src/overlays/actors/ovl_En_Ishi/z_en_ishi.c b/soh/src/overlays/actors/ovl_En_Ishi/z_en_ishi.c index 37dc1fa9db7..a22e0812845 100644 --- a/soh/src/overlays/actors/ovl_En_Ishi/z_en_ishi.c +++ b/soh/src/overlays/actors/ovl_En_Ishi/z_en_ishi.c @@ -8,6 +8,7 @@ #include "overlays/effects/ovl_Effect_Ss_Kakera/z_eff_ss_kakera.h" #include "objects/gameplay_field_keep/gameplay_field_keep.h" #include "soh/OTRGlobals.h" +#include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h" #include "vt.h" @@ -251,7 +252,7 @@ void EnIshi_SpawnDustLarge(EnIshi* this, PlayState* play) { void EnIshi_DropCollectible(EnIshi* this, PlayState* play) { s16 dropParams; - if ((this->actor.params & 1) == ROCK_SMALL) { + if (GameInteractor_Should(VB_ROCK_DROP_ITEM, (this->actor.params & 1) == ROCK_SMALL, this)) { dropParams = (this->actor.params >> 8) & 0xF; if (dropParams >= 0xD) { @@ -335,7 +336,8 @@ void EnIshi_Init(Actor* thisx, PlayState* play) { // If dungeon entrance randomizer is on, remove the grey boulders that normally // block child Link from reaching the Fire Temple entrance. if (type == ROCK_LARGE && IS_RANDO && - Randomizer_GetSettingValue(RSK_SHUFFLE_DUNGEON_ENTRANCES) != RO_DUNGEON_ENTRANCE_SHUFFLE_OFF && + (Randomizer_GetSettingValue(RSK_SHUFFLE_DUNGEON_ENTRANCES) != RO_DUNGEON_ENTRANCE_SHUFFLE_OFF || + CVarGetInteger(CVAR_REMOTE_ARCHIPELAGO("ShuffleEntrances"), 0)) && play->sceneNum == SCENE_DEATH_MOUNTAIN_CRATER) { // Death Mountain Creater Actor_Kill(&this->actor); } diff --git a/soh/src/overlays/actors/ovl_En_Jj/z_en_jj.c b/soh/src/overlays/actors/ovl_En_Jj/z_en_jj.c index 9c485f501d3..e1db3287090 100644 --- a/soh/src/overlays/actors/ovl_En_Jj/z_en_jj.c +++ b/soh/src/overlays/actors/ovl_En_Jj/z_en_jj.c @@ -8,6 +8,9 @@ #include "objects/object_jj/object_jj.h" #include "overlays/actors/ovl_Eff_Dust/z_eff_dust.h" #include "soh/ResourceManagerHelpers.h" +#include "soh_assets.h" + +#include "soh/Enhancements/Holiday/Archez.h" #define FLAGS (ACTOR_FLAG_UPDATE_CULLING_DISABLED | ACTOR_FLAG_DRAW_CULLING_DISABLED) @@ -307,6 +310,26 @@ void EnJj_Update(Actor* thisx, PlayState* play) { this->skelAnime.jointTable[10].z = this->mouthOpenAngle; } +s32 EnJj_PostLimbDraw(PlayState* play, s32 limbIndex, Gfx** dList, Vec3s* rot, void* thisx) { + EnJj* this = (EnJj*)thisx; + + if (CVarGetInteger("gHoliday.Visual.Hats", 0)) { + if (limbIndex == 13) { + OPEN_DISPS(play->state.gfxCtx); + Matrix_Push(); + Matrix_RotateZYX(4649, -8635, 15276, MTXMODE_APPLY); + Matrix_Translate(27.027f, 135.135f, -81.081f, MTXMODE_APPLY); + Matrix_Scale(0.304f, 0.304f, 0.304f, MTXMODE_APPLY); + gSPMatrix(POLY_OPA_DISP++, MATRIX_NEWMTX(play->state.gfxCtx), G_MTX_NOPUSH | G_MTX_LOAD | G_MTX_MODELVIEW); + gSPDisplayList(POLY_OPA_DISP++, gSantaHatGenericDL); + Matrix_Pop(); + CLOSE_DISPS(play->state.gfxCtx); + } + } + + return false; +} + void EnJj_Draw(Actor* thisx, PlayState* play2) { static void* eyeTextures[] = { gJabuJabuEyeOpenTex, gJabuJabuEyeHalfTex, gJabuJabuEyeClosedTex }; PlayState* play = play2; @@ -318,7 +341,8 @@ void EnJj_Draw(Actor* thisx, PlayState* play2) { Matrix_Translate(0.0f, (cosf(this->skelAnime.curFrame * (M_PI / 41.0f)) * 10.0f) - 10.0f, 0.0f, MTXMODE_APPLY); Matrix_Scale(10.0f, 10.0f, 10.0f, MTXMODE_APPLY); gSPSegment(POLY_OPA_DISP++, 0x08, SEGMENTED_TO_VIRTUAL(eyeTextures[this->eyeIndex])); - SkelAnime_DrawSkeletonOpa(play, &this->skelAnime, NULL, NULL, this); + SkipOverrideNextSkeleton(); + SkelAnime_DrawSkeletonOpa(play, &this->skelAnime, NULL, EnJj_PostLimbDraw, this); CLOSE_DISPS(play->state.gfxCtx); } diff --git a/soh/src/overlays/actors/ovl_En_Ko/z_en_ko.c b/soh/src/overlays/actors/ovl_En_Ko/z_en_ko.c index 70ca84c0e25..f17a93e296a 100644 --- a/soh/src/overlays/actors/ovl_En_Ko/z_en_ko.c +++ b/soh/src/overlays/actors/ovl_En_Ko/z_en_ko.c @@ -1351,6 +1351,7 @@ Gfx* EnKo_SetEnvColor(GraphicsContext* gfxCtx, u8 r, u8 g, u8 b, u8 a) { void EnKo_Draw(Actor* thisx, PlayState* play) { EnKo* this = (EnKo*)thisx; Color_RGBA8 tunicColor = sModelInfo[ENKO_TYPE].tunicColor; + Color_RGBA8 bootsColor = sModelInfo[ENKO_TYPE].bootsColor; if (CVarGetInteger(CVAR_COSMETIC("NPC.Kokiri.Changed"), 0)) { diff --git a/soh/src/overlays/actors/ovl_En_Ma1/z_en_ma1.c b/soh/src/overlays/actors/ovl_En_Ma1/z_en_ma1.c index b75aa749f6e..c8606d5db9d 100644 --- a/soh/src/overlays/actors/ovl_En_Ma1/z_en_ma1.c +++ b/soh/src/overlays/actors/ovl_En_Ma1/z_en_ma1.c @@ -8,6 +8,7 @@ #include "objects/object_ma1/object_ma1.h" #include "soh/OTRGlobals.h" #include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h" +#include "soh_assets.h" #define FLAGS \ (ACTOR_FLAG_ATTENTION_ENABLED | ACTOR_FLAG_FRIENDLY | ACTOR_FLAG_UPDATE_CULLING_DISABLED | \ @@ -467,6 +468,21 @@ void EnMa1_PostLimbDraw(PlayState* play, s32 limbIndex, Gfx** dList, Vec3s* rot, if (limbIndex == 15) { Matrix_MultVec3f(&vec, &this->actor.focus.pos); } + + if (CVarGetInteger("gHoliday.Visual.Hats", 0)) { + if (limbIndex == 15) { + OPEN_DISPS(play->state.gfxCtx); + Matrix_Push(); + Matrix_RotateZYX(0, 0, 0, MTXMODE_APPLY); + Matrix_Translate(756.757f, 0.0f, 0.0f, MTXMODE_APPLY); + Matrix_Scale(0.73f, 0.73f, 0.73f, MTXMODE_APPLY); + gDPSetEnvColor(POLY_OPA_DISP++, 0, 0, 255, 255); + gSPMatrix(POLY_OPA_DISP++, MATRIX_NEWMTX(play->state.gfxCtx), G_MTX_NOPUSH | G_MTX_LOAD | G_MTX_MODELVIEW); + gSPDisplayList(POLY_OPA_DISP++, gPaperCrownGenericDL); + Matrix_Pop(); + CLOSE_DISPS(play->state.gfxCtx); + } + } } void EnMa1_Draw(Actor* thisx, PlayState* play) { diff --git a/soh/src/overlays/actors/ovl_En_Mb/z_en_mb.c b/soh/src/overlays/actors/ovl_En_Mb/z_en_mb.c index 78f75dabd5a..72e873050aa 100644 --- a/soh/src/overlays/actors/ovl_En_Mb/z_en_mb.c +++ b/soh/src/overlays/actors/ovl_En_Mb/z_en_mb.c @@ -9,6 +9,8 @@ #include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h" #include "soh/ResourceManagerHelpers.h" +#include "soh/Enhancements/Holiday/Archez.h" + /* * This actor can have three behaviors: * - "Spear Guard" (variable -1): uses a spear, walks around home point, charges player if too close @@ -1553,6 +1555,22 @@ void EnMb_PostLimbDraw(PlayState* play, s32 limbIndex, Gfx** dList, Vec3s* rot, } } +s32 EnMb_OverrideLimbDraw(PlayState* play, s32 limbIndex, Gfx** dList, Vec3f* pos, Vec3s* rot, void* thisx) { + EnMb* this = (EnMb*)thisx; + + if (this->actor.params == ENMB_TYPE_CLUB) { + if (limbIndex == ENMB_LIMB_LHAND) { + SkipOverrideNextLimb(); + } + } else { + if (limbIndex == ENMB_LIMB_RHAND) { + SkipOverrideNextLimb(); + } + } + + return 0; +} + void EnMb_Draw(Actor* thisx, PlayState* play) { static Vec3f frontShieldingTriModel0[] = { { 4000.0f, 7000.0f, 3500.0f }, @@ -1572,7 +1590,7 @@ void EnMb_Draw(Actor* thisx, PlayState* play) { EnMb* this = (EnMb*)thisx; Gfx_SetupDL_25Opa(play->state.gfxCtx); - SkelAnime_DrawSkeletonOpa(play, &this->skelAnime, NULL, EnMb_PostLimbDraw, thisx); + SkelAnime_DrawSkeletonOpa(play, &this->skelAnime, EnMb_OverrideLimbDraw, EnMb_PostLimbDraw, thisx); if (thisx->params != ENMB_TYPE_CLUB) { if (this->attack > ENMB_ATTACK_NONE) { diff --git a/soh/src/overlays/actors/ovl_En_Niw/z_en_niw.c b/soh/src/overlays/actors/ovl_En_Niw/z_en_niw.c index c258182d5c8..3a9a6905cee 100644 --- a/soh/src/overlays/actors/ovl_En_Niw/z_en_niw.c +++ b/soh/src/overlays/actors/ovl_En_Niw/z_en_niw.c @@ -10,6 +10,7 @@ #include "vt.h" #include "soh/frame_interpolation.h" #include "soh/ResourceManagerHelpers.h" +#include "soh_assets.h" #define FLAGS (ACTOR_FLAG_UPDATE_CULLING_DISABLED | ACTOR_FLAG_THROW_ONLY) @@ -1133,13 +1134,33 @@ s32 EnNiw_OverrideLimbDraw(PlayState* play, s32 limbIndex, Gfx** dList, Vec3f* p return false; } +s32 EnNiw_PostLimbDraw(PlayState* play, s32 limbIndex, Gfx** dList, Vec3s* rot, void* thisx) { + EnNiw* this = (EnNiw*)thisx; + + if (CVarGetInteger("gHoliday.Visual.Hats", 0)) { + if (limbIndex == 15) { + OPEN_DISPS(play->state.gfxCtx); + Matrix_Push(); + Matrix_RotateZYX(0, 0, -19705, MTXMODE_APPLY); + Matrix_Translate(297.297f, -81.082f, 0.0f, MTXMODE_APPLY); + Matrix_Scale(1.0f, 1.0f, 1.0f, MTXMODE_APPLY); + gSPMatrix(POLY_OPA_DISP++, MATRIX_NEWMTX(play->state.gfxCtx), G_MTX_NOPUSH | G_MTX_LOAD | G_MTX_MODELVIEW); + gSPDisplayList(POLY_OPA_DISP++, gSantaHatGenericDL); + Matrix_Pop(); + CLOSE_DISPS(play->state.gfxCtx); + } + } + + return false; +} + void EnNiw_Draw(Actor* thisx, PlayState* play) { EnNiw* this = (EnNiw*)thisx; Vec3f scale = { 0.15f, 0.15f, 0.15f }; GraphicsContext* gfxCtx = play->state.gfxCtx; Gfx_SetupDL_25Opa(play->state.gfxCtx); - SkelAnime_DrawSkeletonOpa(play, &this->skelAnime, EnNiw_OverrideLimbDraw, NULL, this); + SkelAnime_DrawSkeletonOpa(play, &this->skelAnime, EnNiw_OverrideLimbDraw, EnNiw_PostLimbDraw, this); if (this->actionFunc == func_80AB6450) { func_80033C30(&this->actor.world.pos, &scale, 255, play); diff --git a/soh/src/overlays/actors/ovl_En_Niw_Girl/z_en_niw_girl.c b/soh/src/overlays/actors/ovl_En_Niw_Girl/z_en_niw_girl.c index 84da935d671..6eeeaa22eae 100644 --- a/soh/src/overlays/actors/ovl_En_Niw_Girl/z_en_niw_girl.c +++ b/soh/src/overlays/actors/ovl_En_Niw_Girl/z_en_niw_girl.c @@ -8,6 +8,7 @@ #include "objects/object_gr/object_gr.h" #include "vt.h" #include "soh/ResourceManagerHelpers.h" +#include "soh_assets.h" #define FLAGS (ACTOR_FLAG_ATTENTION_ENABLED | ACTOR_FLAG_FRIENDLY | ACTOR_FLAG_UPDATE_CULLING_DISABLED) @@ -247,6 +248,27 @@ s32 EnNiwGirlOverrideLimbDraw(PlayState* play, s32 limbIndex, Gfx** dList, Vec3f static Vec3f sConstVec3f = { 0.2f, 0.2f, 0.2f }; +s32 EnNiwGirl_PostLimbDraw(PlayState* play, s32 limbIndex, Gfx** dList, Vec3s* rot, void* thisx) { + EnNiwGirl* this = (EnNiwGirl*)thisx; + + if (CVarGetInteger("gHoliday.Visual.Hats", 0)) { + if (limbIndex == 4) { + OPEN_DISPS(play->state.gfxCtx); + Matrix_Push(); + Matrix_RotateZYX(0, 0, 0, MTXMODE_APPLY); + Matrix_Translate(945.945f, 0.0f, 0.0f, MTXMODE_APPLY); + Matrix_Scale(0.676f, 0.676f, 0.676f, MTXMODE_APPLY); + gDPSetEnvColor(POLY_OPA_DISP++, 255, 0, 255, 255); + gSPMatrix(POLY_OPA_DISP++, MATRIX_NEWMTX(play->state.gfxCtx), G_MTX_NOPUSH | G_MTX_LOAD | G_MTX_MODELVIEW); + gSPDisplayList(POLY_OPA_DISP++, gPaperCrownGenericDL); + Matrix_Pop(); + CLOSE_DISPS(play->state.gfxCtx); + } + } + + return false; +} + void EnNiwGirl_Draw(Actor* thisx, PlayState* play) { static void* eyeTextures[] = { gNiwGirlEyeOpenTex, gNiwGirlEyeHalfTex, gNiwGirlEyeClosedTex }; EnNiwGirl* this = (EnNiwGirl*)thisx; @@ -257,7 +279,7 @@ void EnNiwGirl_Draw(Actor* thisx, PlayState* play) { Gfx_SetupDL_25Opa(play->state.gfxCtx); gSPSegment(POLY_OPA_DISP++, 0x08, SEGMENTED_TO_VIRTUAL(eyeTextures[this->eyeIndex])); - SkelAnime_DrawSkeletonOpa(play, &this->skelAnime, EnNiwGirlOverrideLimbDraw, NULL, this); + SkelAnime_DrawSkeletonOpa(play, &this->skelAnime, EnNiwGirlOverrideLimbDraw, EnNiwGirl_PostLimbDraw, this); func_80033C30(&this->actor.world.pos, &sp4C, 255, play); CLOSE_DISPS(play->state.gfxCtx); diff --git a/soh/src/overlays/actors/ovl_En_Niw_Lady/z_en_niw_lady.c b/soh/src/overlays/actors/ovl_En_Niw_Lady/z_en_niw_lady.c index 69cc15aeda5..b888f184547 100644 --- a/soh/src/overlays/actors/ovl_En_Niw_Lady/z_en_niw_lady.c +++ b/soh/src/overlays/actors/ovl_En_Niw_Lady/z_en_niw_lady.c @@ -6,6 +6,7 @@ #include "soh/OTRGlobals.h" #include "soh/ResourceManagerHelpers.h" #include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h" +#include "soh_assets.h" #define FLAGS (ACTOR_FLAG_ATTENTION_ENABLED | ACTOR_FLAG_FRIENDLY | ACTOR_FLAG_UPDATE_CULLING_DISABLED) @@ -586,6 +587,27 @@ s32 EnNiwLady_OverrideLimbDraw(PlayState* play, s32 limbIndex, Gfx** dList, Vec3 return false; } +s32 EnNiwLady_PostLimbDraw(PlayState* play, s32 limbIndex, Gfx** dList, Vec3s* rot, void* thisx) { + EnNiwLady* this = (EnNiwLady*)thisx; + + if (CVarGetInteger("gHoliday.Visual.Hats", 0)) { + if (limbIndex == 15) { + OPEN_DISPS(play->state.gfxCtx); + Matrix_Push(); + Matrix_RotateZYX(-886, -3322, -5093, MTXMODE_APPLY); + Matrix_Translate(824.324f, 283.782f, -202.703f, MTXMODE_APPLY); + Matrix_Scale(0.762f, 0.762f, 0.762f, MTXMODE_APPLY); + gDPSetEnvColor(POLY_OPA_DISP++, 0, 0, 255, 255); + gSPMatrix(POLY_OPA_DISP++, MATRIX_NEWMTX(play->state.gfxCtx), G_MTX_NOPUSH | G_MTX_LOAD | G_MTX_MODELVIEW); + gSPDisplayList(POLY_OPA_DISP++, gPaperCrownGenericDL); + Matrix_Pop(); + CLOSE_DISPS(play->state.gfxCtx); + } + } + + return false; +} + void EnNiwLady_Draw(Actor* thisx, PlayState* play) { static void* sEyeTextures[] = { gCuccoLadyEyeOpenTex, gCuccoLadyEyeHalfTex, gCuccoLadyEyeClosedTex }; EnNiwLady* this = (EnNiwLady*)thisx; @@ -597,7 +619,7 @@ void EnNiwLady_Draw(Actor* thisx, PlayState* play) { gDPSetEnvColor(POLY_OPA_DISP++, 0, 0, 0, 255); gSPSegment(POLY_OPA_DISP++, 0x08, SEGMENTED_TO_VIRTUAL(sEyeTextures[this->faceState])); gSPSegment(POLY_OPA_DISP++, 0x0C, func_80ABB0A0(play->state.gfxCtx)); - SkelAnime_DrawSkeletonOpa(play, &this->skelAnime, EnNiwLady_OverrideLimbDraw, NULL, this); + SkelAnime_DrawSkeletonOpa(play, &this->skelAnime, EnNiwLady_OverrideLimbDraw, EnNiwLady_PostLimbDraw, this); } CLOSE_DISPS(play->state.gfxCtx); } diff --git a/soh/src/overlays/actors/ovl_En_Nutsball/z_en_nutsball.c b/soh/src/overlays/actors/ovl_En_Nutsball/z_en_nutsball.c index cf70efe9f84..eae576a8164 100644 --- a/soh/src/overlays/actors/ovl_En_Nutsball/z_en_nutsball.c +++ b/soh/src/overlays/actors/ovl_En_Nutsball/z_en_nutsball.c @@ -12,6 +12,7 @@ #include "objects/object_shopnuts/object_shopnuts.h" #include "objects/object_dns/object_dns.h" #include "objects/object_dnk/object_dnk.h" +#include "assets/objects/gameplay_keep/gameplay_keep.h" #define FLAGS ACTOR_FLAG_UPDATE_CULLING_DISABLED @@ -75,7 +76,7 @@ void EnNutsball_Init(Actor* thisx, PlayState* play) { ActorShape_Init(&this->actor.shape, 400.0f, ActorShadow_DrawCircle, 13.0f); Collider_InitCylinder(play, &this->collider); Collider_SetCylinder(play, &this->collider, &this->actor, &sCylinderInit); - if (CVarGetInteger(CVAR_ENHANCEMENT("RandomizedEnemies"), 0)) { + if (CVarGetInteger(CVAR_ENHANCEMENT("RandomizedEnemies"), 0) || CVarGetInteger(CVAR_GENERAL("LetItSnow"), 0)) { this->objBankIndex = 0; } else { this->objBankIndex = Object_GetIndex(&play->objectCtx, sObjectIDs[this->actor.params]); @@ -141,8 +142,10 @@ void func_80ABBBA8(EnNutsball* this, PlayState* play) { sp40.y = this->actor.world.pos.y + 4; sp40.z = this->actor.world.pos.z; - EffectSsHahen_SpawnBurst(play, &sp40, 6.0f, 0, 7, 3, 15, HAHEN_OBJECT_DEFAULT, 10, NULL); - SoundSource_PlaySfxAtFixedWorldPos(play, &this->actor.world.pos, 20, NA_SE_EN_OCTAROCK_ROCK); + /*EffectSsHahen_SpawnBurst(play, &sp40, 6.0f, 0, 7, 3, 15, HAHEN_OBJECT_DEFAULT, 10, NULL); + SoundSource_PlaySfxAtFixedWorldPos(play, &this->actor.world.pos, 20, NA_SE_EN_OCTAROCK_ROCK);*/ + EffectSsIcePiece_SpawnBurst(play, &this->actor.world.pos, this->actor.scale.x / 10); + SoundSource_PlaySfxAtFixedWorldPos(play, &this->actor.world.pos, 20, NA_SE_PL_ICE_BROKEN); Actor_Kill(&this->actor); } else { if (this->timer == -300) { @@ -178,15 +181,34 @@ void EnNutsball_Draw(Actor* thisx, PlayState* play) { OPEN_DISPS(play->state.gfxCtx); - if (CVarGetInteger(CVAR_ENHANCEMENT("NewDrops"), 0) != 0) { + // if ((CVarGetInteger(CVAR_ENHANCEMENT("NewDrops"), 0) != 0) || CVarGetInteger(CVAR_GENERAL("LetItSnow"), 0)) { + // Gfx_SetupDL_25Opa(play->state.gfxCtx); + // f32 scale = 12.0f; + + // gSPSegment(POLY_OPA_DISP++, 0x08, + // Gfx_TwoTexScroll(play->state.gfxCtx, 0, 0, (0 - 1) % 128, 32, 32, 1, 0, (1 * -2) % 128, 32, 32)); + + // Matrix_RotateX(thisx->home.rot.z * 9.58738e-05f, MTXMODE_APPLY); + // Matrix_Translate(0.0f, -445.946f, 0.0f, MTXMODE_APPLY); + // Matrix_Scale(scale, scale, scale, MTXMODE_APPLY); + + // gSPMatrix(POLY_OPA_DISP++, MATRIX_NEWMTX(play->state.gfxCtx), G_MTX_NOPUSH | G_MTX_LOAD | G_MTX_MODELVIEW); + // gDPSetEnvColor(POLY_OPA_DISP++, 0, 50, 100, 255); + // gSPDisplayList(POLY_OPA_DISP++, gEffIceFragment3DL); + if ((CVarGetInteger(CVAR_ENHANCEMENT("NewDrops"), 0) != 0) || CVarGetInteger(CVAR_GENERAL("LetItSnow"), 0)) { Gfx_SetupDL_25Opa(play->state.gfxCtx); + f32 scale = 12.0f; + gSPSegment(POLY_OPA_DISP++, 0x08, - Gfx_TwoTexScroll(play->state.gfxCtx, 0, 1 * (play->state.frames * 6), 1 * (play->state.frames * 6), - 32, 32, 1, 1 * (play->state.frames * 6), 1 * (play->state.frames * 6), 32, 32)); - Matrix_Scale(25.0f, 25.0f, 25.0f, MTXMODE_APPLY); + Gfx_TwoTexScroll(play->state.gfxCtx, 0, 0, (0 - 1) % 128, 32, 32, 1, 0, (1 * -2) % 128, 32, 32)); + Matrix_RotateX(thisx->home.rot.z * 9.58738e-05f, MTXMODE_APPLY); - gSPMatrix(POLY_OPA_DISP++, MATRIX_NEWMTX(play->state.gfxCtx), G_MTX_MODELVIEW | G_MTX_LOAD); - gSPDisplayList(POLY_OPA_DISP++, sDListsNew[thisx->params]); + Matrix_Translate(0.0f, -445.946f, 0.0f, MTXMODE_APPLY); + Matrix_Scale(scale, scale, scale, MTXMODE_APPLY); + + gSPMatrix(POLY_OPA_DISP++, MATRIX_NEWMTX(play->state.gfxCtx), G_MTX_NOPUSH | G_MTX_LOAD | G_MTX_MODELVIEW); + gDPSetEnvColor(POLY_OPA_DISP++, 0, 50, 100, 255); + gSPDisplayList(POLY_OPA_DISP++, gEffIceFragment3DL); } else { Gfx_SetupDL_25Opa(play->state.gfxCtx); Matrix_Mult(&play->billboardMtxF, MTXMODE_APPLY); diff --git a/soh/src/overlays/actors/ovl_En_Okuta/z_en_okuta.c b/soh/src/overlays/actors/ovl_En_Okuta/z_en_okuta.c index 279bc68343a..91f3b943c2d 100644 --- a/soh/src/overlays/actors/ovl_En_Okuta/z_en_okuta.c +++ b/soh/src/overlays/actors/ovl_En_Okuta/z_en_okuta.c @@ -3,6 +3,7 @@ #include "objects/gameplay_field_keep/gameplay_field_keep.h" #include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h" #include "soh/ResourceManagerHelpers.h" +#include "assets/objects/gameplay_keep/gameplay_keep.h" #define FLAGS (ACTOR_FLAG_ATTENTION_ENABLED | ACTOR_FLAG_HOSTILE) @@ -520,7 +521,7 @@ void EnOkuta_ProjectileFly(EnOkuta* this, PlayState* play) { f32 temp_f20; f32 temp_f22; s32 i; - for (s16 i = 0; i < ARRAY_COUNT(sEffectScales); i++) { + /*for (s16 i = 0; i < ARRAY_COUNT(sEffectScales); i++) { phi_s0 += 10000; temp_f20 = Rand_ZeroOne() * 5.0f; @@ -547,12 +548,14 @@ void EnOkuta_ProjectileFly(EnOkuta* this, PlayState* play) { EffectSsKakera_Spawn(play, &pos, &velocity, &this->actor.world.pos, gravity, phi_v0, 30, 5, 0, sEffectScales[i] / 5, 3, 0, 70, 1, OBJECT_GAMEPLAY_FIELD_KEEP, gSilverRockFragmentsDL); - } + }*/ + EffectSsIcePiece_SpawnBurst(play, &this->actor.world.pos, this->actor.scale.x / 10); } else { EffectSsHahen_SpawnBurst(play, &pos, 6.0f, 0, 1, 2, 15, 7, 10, gOctorokProjectileDL); } - SoundSource_PlaySfxAtFixedWorldPos(play, &this->actor.world.pos, 20, NA_SE_EN_OCTAROCK_ROCK); + // SoundSource_PlaySfxAtFixedWorldPos(play, &this->actor.world.pos, 20, NA_SE_EN_OCTAROCK_ROCK); + SoundSource_PlaySfxAtFixedWorldPos(play, &this->actor.world.pos, 20, NA_SE_PL_ICE_BROKEN); Actor_Kill(&this->actor); } } else if (this->timer == -300) { @@ -764,16 +767,30 @@ void EnOkuta_Draw(Actor* thisx, PlayState* play) { } else { OPEN_DISPS(play->state.gfxCtx); - if (CVarGetInteger(CVAR_ENHANCEMENT("NewDrops"), 0) != 0) { + // if (CVarGetInteger(CVAR_ENHANCEMENT("NewDrops"), 0) != 0) { + // Gfx_SetupDL_25Opa(play->state.gfxCtx); + // gSPSegment(POLY_OPA_DISP++, 0x08, + // Gfx_TwoTexScroll(play->state.gfxCtx, 0, 1 * (play->state.frames * 6), + // 1 * (play->state.frames * 6), 32, 32, 1, 1 * (play->state.frames * 6), + // 1 * (play->state.frames * 6), 32, 32)); + // Matrix_Scale(7.0f, 7.0f, 7.0f, MTXMODE_APPLY); + // Matrix_RotateX(thisx->home.rot.z * (M_PI / 0x8000), MTXMODE_APPLY); + // gSPMatrix(POLY_OPA_DISP++, MATRIX_NEWMTX(play->state.gfxCtx), G_MTX_MODELVIEW | G_MTX_LOAD); + // gSPDisplayList(POLY_OPA_DISP++, gSilverRockDL); + if ((CVarGetInteger(CVAR_ENHANCEMENT("NewDrops"), 0) != 0) || CVarGetInteger(CVAR_GENERAL("LetItSnow"), 0)) { Gfx_SetupDL_25Opa(play->state.gfxCtx); + f32 scale = 12.0f; + gSPSegment(POLY_OPA_DISP++, 0x08, - Gfx_TwoTexScroll(play->state.gfxCtx, 0, 1 * (play->state.frames * 6), - 1 * (play->state.frames * 6), 32, 32, 1, 1 * (play->state.frames * 6), - 1 * (play->state.frames * 6), 32, 32)); - Matrix_Scale(7.0f, 7.0f, 7.0f, MTXMODE_APPLY); - Matrix_RotateX(thisx->home.rot.z * (M_PI / 0x8000), MTXMODE_APPLY); - gSPMatrix(POLY_OPA_DISP++, MATRIX_NEWMTX(play->state.gfxCtx), G_MTX_MODELVIEW | G_MTX_LOAD); - gSPDisplayList(POLY_OPA_DISP++, gSilverRockDL); + Gfx_TwoTexScroll(play->state.gfxCtx, 0, 0, (0 - 1) % 128, 32, 32, 1, 0, (1 * -2) % 128, 32, 32)); + + Matrix_RotateX(thisx->home.rot.z * 9.58738e-05f, MTXMODE_APPLY); + Matrix_Translate(0.0f, -445.946f, 0.0f, MTXMODE_APPLY); + Matrix_Scale(scale, scale, scale, MTXMODE_APPLY); + + gSPMatrix(POLY_OPA_DISP++, MATRIX_NEWMTX(play->state.gfxCtx), G_MTX_NOPUSH | G_MTX_LOAD | G_MTX_MODELVIEW); + gDPSetEnvColor(POLY_OPA_DISP++, 0, 50, 100, 255); + gSPDisplayList(POLY_OPA_DISP++, gEffIceFragment3DL); } else { Matrix_Mult(&play->billboardMtxF, MTXMODE_APPLY); Matrix_RotateZ(this->actor.home.rot.z * (M_PI / 0x8000), MTXMODE_APPLY); diff --git a/soh/src/overlays/actors/ovl_En_Ossan/z_en_ossan.c b/soh/src/overlays/actors/ovl_En_Ossan/z_en_ossan.c index d78daafe457..a809be282a9 100644 --- a/soh/src/overlays/actors/ovl_En_Ossan/z_en_ossan.c +++ b/soh/src/overlays/actors/ovl_En_Ossan/z_en_ossan.c @@ -18,6 +18,7 @@ #include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h" #include #include "soh/OTRGlobals.h" +#include "soh_assets.h" #define FLAGS (ACTOR_FLAG_ATTENTION_ENABLED | ACTOR_FLAG_FRIENDLY | ACTOR_FLAG_UPDATE_CULLING_DISABLED) @@ -2425,6 +2426,73 @@ void EnOssan_DrawStickDirectionPrompts(PlayState* play, EnOssan* this) { CLOSE_DISPS(play->state.gfxCtx); } +s32 EnOssan_PostLimbDraw(PlayState* play, s32 limbIndex, Gfx** dList, Vec3s* rot, void* thisx) { + EnOssan* this = (EnOssan*)thisx; + + if (CVarGetInteger("gHoliday.Visual.Hats", 0)) { + if (limbIndex == 8) { + switch (this->actor.params) { + case 4: { + OPEN_DISPS(play->state.gfxCtx); + Matrix_Push(); + Matrix_RotateZYX(-11071, -443, -3986, MTXMODE_APPLY); + Matrix_Translate(878.378f, 351.351f, 540.541f, MTXMODE_APPLY); + Matrix_Scale(1.352f, 1.352f, 1.352f, MTXMODE_APPLY); + gSPMatrix(POLY_OPA_DISP++, MATRIX_NEWMTX(play->state.gfxCtx), + G_MTX_NOPUSH | G_MTX_LOAD | G_MTX_MODELVIEW); + gSPDisplayList(POLY_OPA_DISP++, gSantaHatGenericDL); + Matrix_Pop(); + CLOSE_DISPS(play->state.gfxCtx); + break; + } + case 1: + case 3: { + OPEN_DISPS(play->state.gfxCtx); + Matrix_Push(); + Matrix_RotateZYX(-6643, 1992, -1772, MTXMODE_APPLY); + Matrix_Translate(918.919f, 121.622f, 256.757f, MTXMODE_APPLY); + Matrix_Scale(0.73f, 0.73f, 0.73f, MTXMODE_APPLY); + gDPSetEnvColor(POLY_OPA_DISP++, 0, 255, 0, 255); + gSPMatrix(POLY_OPA_DISP++, MATRIX_NEWMTX(play->state.gfxCtx), + G_MTX_NOPUSH | G_MTX_LOAD | G_MTX_MODELVIEW); + gSPDisplayList(POLY_OPA_DISP++, gPaperCrownGenericDL); + Matrix_Pop(); + CLOSE_DISPS(play->state.gfxCtx); + break; + } + case 2: { + OPEN_DISPS(play->state.gfxCtx); + Matrix_Push(); + Matrix_RotateZYX(-16163, 0, 2878, MTXMODE_APPLY); + Matrix_Translate(905.406f, 0.0f, -27.027f, MTXMODE_APPLY); + Matrix_Scale(1.318f, 1.318f, 1.318f, MTXMODE_APPLY); + gSPMatrix(POLY_OPA_DISP++, MATRIX_NEWMTX(play->state.gfxCtx), + G_MTX_NOPUSH | G_MTX_LOAD | G_MTX_MODELVIEW); + gSPDisplayList(POLY_OPA_DISP++, gSantaHatGenericDL); + Matrix_Pop(); + CLOSE_DISPS(play->state.gfxCtx); + break; + } + default: { + OPEN_DISPS(play->state.gfxCtx); + Matrix_Push(); + Matrix_RotateZYX(-11071, -443, -3986, MTXMODE_APPLY); + Matrix_Translate(878.378f, 351.351f, 540.541f, MTXMODE_APPLY); + Matrix_Scale(1.352f, 1.352f, 1.352f, MTXMODE_APPLY); + gSPMatrix(POLY_OPA_DISP++, MATRIX_NEWMTX(play->state.gfxCtx), + G_MTX_NOPUSH | G_MTX_LOAD | G_MTX_MODELVIEW); + gSPDisplayList(POLY_OPA_DISP++, gSantaHatGenericDL); + Matrix_Pop(); + CLOSE_DISPS(play->state.gfxCtx); + break; + } + } + } + } + + return false; +} + void EnOssan_DrawBazaarShopkeeper(Actor* thisx, PlayState* play) { static void* sBazaarShopkeeperEyeTextures[] = { gOssanEyeOpenTex, gOssanEyeHalfTex, gOssanEyeClosedTex }; EnOssan* this = (EnOssan*)thisx; @@ -2434,7 +2502,8 @@ void EnOssan_DrawBazaarShopkeeper(Actor* thisx, PlayState* play) { Gfx_SetupDL_25Opa(play->state.gfxCtx); gSPSegment(POLY_OPA_DISP++, 0x08, SEGMENTED_TO_VIRTUAL(sBazaarShopkeeperEyeTextures[this->eyeTextureIdx])); - SkelAnime_DrawSkeletonOpa(play, &this->skelAnime, EnOssan_OverrideLimbDrawDefaultShopkeeper, NULL, this); + SkelAnime_DrawSkeletonOpa(play, &this->skelAnime, EnOssan_OverrideLimbDrawDefaultShopkeeper, EnOssan_PostLimbDraw, + this); EnOssan_DrawCursor(play, this, this->cursorX, this->cursorY, this->cursorZ, this->drawCursor); EnOssan_DrawStickDirectionPrompts(play, this); @@ -2460,6 +2529,16 @@ s32 EnOssan_OverrideLimbDrawKokiriShopkeeper(PlayState* play, s32 limbIndex, Gfx gSPSegment(POLY_OPA_DISP++, 0x0A, SEGMENTED_TO_VIRTUAL(sKokiriShopkeeperEyeTextures[this->eyeTextureIdx])); } + if (limbIndex == 15 && CVarGetInteger("gHoliday.Visual.Hats", 0)) { + Matrix_Push(); + Matrix_RotateZYX(14169, -2215, 0, MTXMODE_APPLY); + Matrix_Translate(1810.811f, -351.351f, -94.595f, MTXMODE_APPLY); + Matrix_Scale(1.068f, 1.068f, 1.068f, MTXMODE_APPLY); + gSPMatrix(POLY_OPA_DISP++, MATRIX_NEWMTX(play->state.gfxCtx), G_MTX_NOPUSH | G_MTX_LOAD | G_MTX_MODELVIEW); + gSPDisplayList(POLY_OPA_DISP++, gSantaHatGenericDL); + Matrix_Pop(); + } + CLOSE_DISPS(play->state.gfxCtx); return 0; @@ -2488,8 +2567,8 @@ void EnOssan_DrawKokiriShopkeeper(Actor* thisx, PlayState* play) { Gfx_SetupDL_25Opa(play->state.gfxCtx); gDPSetEnvColor(POLY_OPA_DISP++, 0, 0, 0, 255); - gSPSegment(POLY_OPA_DISP++, 0x08, EnOssan_SetEnvColor(play->state.gfxCtx, 0, 130, 70, 255)); - gSPSegment(POLY_OPA_DISP++, 0x09, EnOssan_SetEnvColor(play->state.gfxCtx, 110, 170, 20, 255)); + gSPSegment(POLY_OPA_DISP++, 0x08, EnOssan_SetEnvColor(play->state.gfxCtx, 255, 0, 0, 255)); + gSPSegment(POLY_OPA_DISP++, 0x09, EnOssan_SetEnvColor(play->state.gfxCtx, 255, 0, 0, 255)); gSPSegment(POLY_OPA_DISP++, 0x0C, EnOssan_EndDList(play->state.gfxCtx)); SkelAnime_DrawSkeletonOpa(play, &this->skelAnime, EnOssan_OverrideLimbDrawKokiriShopkeeper, NULL, this); @@ -2564,7 +2643,7 @@ void EnOssan_DrawPotionShopkeeper(Actor* thisx, PlayState* play) { Gfx_SetupDL_25Opa(play->state.gfxCtx); gSPSegment(POLY_OPA_DISP++, 0x08, SEGMENTED_TO_VIRTUAL(sPotionShopkeeperEyeTextures[this->eyeTextureIdx])); - SkelAnime_DrawSkeletonOpa(play, &this->skelAnime, NULL, NULL, this); + SkelAnime_DrawSkeletonOpa(play, &this->skelAnime, NULL, EnOssan_PostLimbDraw, this); EnOssan_DrawCursor(play, this, this->cursorX, this->cursorY, this->cursorZ, this->drawCursor); EnOssan_DrawStickDirectionPrompts(play, this); @@ -2600,7 +2679,7 @@ void EnOssan_DrawBombchuShopkeeper(Actor* thisx, PlayState* play) { Gfx_SetupDL_25Opa(play->state.gfxCtx); gSPSegment(POLY_OPA_DISP++, 0x08, SEGMENTED_TO_VIRTUAL(sBombchuShopkeeperEyeTextures[this->eyeTextureIdx])); - SkelAnime_DrawSkeletonOpa(play, &this->skelAnime, NULL, NULL, this); + SkelAnime_DrawSkeletonOpa(play, &this->skelAnime, NULL, EnOssan_PostLimbDraw, this); EnOssan_DrawCursor(play, this, this->cursorX, this->cursorY, this->cursorZ, this->drawCursor); EnOssan_DrawStickDirectionPrompts(play, this); diff --git a/soh/src/overlays/actors/ovl_En_Po_Relay/z_en_po_relay.c b/soh/src/overlays/actors/ovl_En_Po_Relay/z_en_po_relay.c index c62db2dd8a1..b71ac4740bc 100644 --- a/soh/src/overlays/actors/ovl_En_Po_Relay/z_en_po_relay.c +++ b/soh/src/overlays/actors/ovl_En_Po_Relay/z_en_po_relay.c @@ -10,6 +10,7 @@ #include "soh/ResourceManagerHelpers.h" #include "soh/Enhancements/game-interactor/GameInteractor.h" #include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h" +#include "soh_assets.h" #define FLAGS \ (ACTOR_FLAG_ATTENTION_ENABLED | ACTOR_FLAG_FRIENDLY | ACTOR_FLAG_UPDATE_CULLING_DISABLED | \ @@ -431,6 +432,18 @@ void EnPoRelay_PostLimbDraw(PlayState* play, s32 limbIndex, Gfx** dList, Vec3s* gSPDisplayList(POLY_OPA_DISP++, gDampeHaloDL); CLOSE_DISPS(play->state.gfxCtx); } + + if (limbIndex == 16 && CVarGetInteger("gHoliday.Visual.Hats", 0)) { + OPEN_DISPS(play->state.gfxCtx); + Matrix_Push(); + Matrix_RotateZYX(10627, 3321, -13727, MTXMODE_APPLY); + Matrix_Translate(418.919f, 40.54f, -256.757f, MTXMODE_APPLY); + Matrix_Scale(1.068f, 1.068f, 1.068f, MTXMODE_APPLY); + gSPMatrix(POLY_OPA_DISP++, MATRIX_NEWMTX(play->state.gfxCtx), G_MTX_NOPUSH | G_MTX_LOAD | G_MTX_MODELVIEW); + gSPDisplayList(POLY_OPA_DISP++, gSantaHatGenericDL); + Matrix_Pop(); + CLOSE_DISPS(play->state.gfxCtx); + } } void EnPoRelay_Draw(Actor* thisx, PlayState* play) { diff --git a/soh/src/overlays/actors/ovl_En_Shopnuts/z_en_shopnuts.c b/soh/src/overlays/actors/ovl_En_Shopnuts/z_en_shopnuts.c index 5ac3c7e8d89..aec5aa47e71 100644 --- a/soh/src/overlays/actors/ovl_En_Shopnuts/z_en_shopnuts.c +++ b/soh/src/overlays/actors/ovl_En_Shopnuts/z_en_shopnuts.c @@ -4,6 +4,7 @@ #include "soh/OTRGlobals.h" #include "soh/ResourceManagerHelpers.h" #include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h" +#include "soh_assets.h" #define FLAGS (ACTOR_FLAG_ATTENTION_ENABLED | ACTOR_FLAG_HOSTILE) @@ -306,6 +307,18 @@ void EnShopnuts_PostLimbDraw(PlayState* play, s32 limbIndex, Gfx** dList, Vec3s* gSPDisplayList(POLY_OPA_DISP++, gBusinessScrubNoseDL); CLOSE_DISPS(play->state.gfxCtx); } + + if (limbIndex == 17 && CVarGetInteger("gHoliday.Visual.Hats", 0)) { + OPEN_DISPS(play->state.gfxCtx); + Matrix_Push(); + Matrix_RotateZYX(0, 0, 17490, MTXMODE_APPLY); + Matrix_Translate(4200.0f, -472.973f, 0.0f, MTXMODE_APPLY); + Matrix_Scale(4.932f, 4.932f, 4.932f, MTXMODE_APPLY); + gSPMatrix(POLY_OPA_DISP++, MATRIX_NEWMTX(play->state.gfxCtx), G_MTX_NOPUSH | G_MTX_LOAD | G_MTX_MODELVIEW); + gSPDisplayList(POLY_OPA_DISP++, gSantaHatGenericDL); + Matrix_Pop(); + CLOSE_DISPS(play->state.gfxCtx); + } } void EnShopnuts_Draw(Actor* thisx, PlayState* play) { diff --git a/soh/src/overlays/actors/ovl_En_Skb/z_en_skb.c b/soh/src/overlays/actors/ovl_En_Skb/z_en_skb.c index b7612dd9179..a653f6085b1 100644 --- a/soh/src/overlays/actors/ovl_En_Skb/z_en_skb.c +++ b/soh/src/overlays/actors/ovl_En_Skb/z_en_skb.c @@ -3,6 +3,7 @@ #include "objects/object_skb/object_skb.h" #include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h" #include "soh/ResourceManagerHelpers.h" +#include "soh_assets.h" #define FLAGS (ACTOR_FLAG_ATTENTION_ENABLED | ACTOR_FLAG_HOSTILE | ACTOR_FLAG_UPDATE_CULLING_DISABLED) @@ -554,6 +555,19 @@ void EnSkb_PostLimbDraw(PlayState* play, s32 limbIndex, Gfx** dList, Vec3s* rot, } else if ((this->unk_283 ^ (this->unk_283 | 4)) == 0) { BodyBreak_SetInfo(&this->bodyBreak, limbIndex, 0, 18, 18, dList, BODYBREAK_OBJECT_DEFAULT); } + + if (limbIndex == 11 && CVarGetInteger("gHoliday.Visual.Hats", 0)) { + OPEN_DISPS(play->state.gfxCtx); + Matrix_Push(); + Matrix_RotateZYX(0, 0, -2215, MTXMODE_APPLY); + Matrix_Translate(1324.324f, 662.162f, 0.0f, MTXMODE_APPLY); + Matrix_Scale(1.0f, 1.0f, 1.0f, MTXMODE_APPLY); + gDPSetEnvColor(POLY_OPA_DISP++, 255, 0, 0, 255); + gSPMatrix(POLY_OPA_DISP++, MATRIX_NEWMTX(play->state.gfxCtx), G_MTX_NOPUSH | G_MTX_LOAD | G_MTX_MODELVIEW); + gSPDisplayList(POLY_OPA_DISP++, gPaperCrownGenericDL); + Matrix_Pop(); + CLOSE_DISPS(play->state.gfxCtx); + } } void EnSkb_Draw(Actor* thisx, PlayState* play) { diff --git a/soh/src/overlays/actors/ovl_En_Snowball/z_en_snowball.c b/soh/src/overlays/actors/ovl_En_Snowball/z_en_snowball.c new file mode 100644 index 00000000000..e2781c89173 --- /dev/null +++ b/soh/src/overlays/actors/ovl_En_Snowball/z_en_snowball.c @@ -0,0 +1,230 @@ +/* + * File: z_en_snowball.c + * Overlay: ovl_En_Snowball + * Description: Rollable Snowball + */ + +#include "z_en_snowball.h" +#include "objects/gameplay_keep/gameplay_keep.h" +#include "objects/object_goroiwa/object_goroiwa.h" +#include "soh_assets.h" + +#define FLAGS ACTOR_FLAG_UPDATE_WHILE_CULLED + +void EnSnowball_Init(Actor* thisx, PlayState* play); +void EnSnowball_Destroy(Actor* thisx, PlayState* play); +void EnSnowball_Update(Actor* thisx, PlayState* play); +void EnSnowball_Draw(Actor* thisx, PlayState* play); + +static ColliderJntSphElementInit sJntSphElementsInit[] = { + { + { + ELEMTYPE_UNK0, + { 0x20000000, 0x00, 0x04 }, + { 0x00000000, 0x00, 0x00 }, + TOUCH_ON | TOUCH_SFX_NORMAL, + BUMP_NONE, + OCELEM_ON, + }, + { 0, { { 0, 0, 0 }, 14 }, 100 }, + }, +}; + +static ColliderJntSphInit sJntSphInit = { + { + COLTYPE_NONE, + AT_ON | AT_TYPE_ENEMY, + AC_NONE, + OC1_ON | OC1_TYPE_ALL, + OC2_TYPE_2, + COLSHAPE_JNTSPH, + }, + 1, + sJntSphElementsInit, +}; + +static CollisionCheckInfoInit sColChkInfoInit = { 0, 3, 15, MASS_HEAVY }; + +void EnSnowball_UpdateCollider(EnSnowball* this) { + Sphere16* worldSphere = &this->collider.elements[0].dim.worldSphere; + + worldSphere->center.x = this->actor.world.pos.x; + worldSphere->center.y = this->actor.world.pos.y + (this->actor.scale.x * 500.0f); + worldSphere->center.z = this->actor.world.pos.z; + worldSphere->radius = (this->actor.scale.x * 500.0f); +} + +void EnSnowball_InitCollider(EnSnowball* this, PlayState* play) { + Collider_InitJntSph(play, &this->collider); + Collider_SetJntSph(play, &this->collider, &this->actor, &sJntSphInit, this->colliderItems); + EnSnowball_UpdateCollider(this); + this->collider.elements[0].dim.worldSphere.radius = (this->actor.scale.x * 500.0f); +} + +static InitChainEntry sInitChain[] = { + ICHAIN_F32_DIV1000(gravity, -860, ICHAIN_CONTINUE), ICHAIN_F32_DIV1000(minVelocityY, -15000, ICHAIN_CONTINUE), + ICHAIN_VEC3F_DIV1000(scale, 5, ICHAIN_CONTINUE), ICHAIN_F32(uncullZoneForward, 1500, ICHAIN_CONTINUE), + ICHAIN_F32(uncullZoneScale, 150, ICHAIN_CONTINUE), ICHAIN_F32(uncullZoneDownward, 1500, ICHAIN_STOP), +}; + +void EnSnowball_Init(Actor* thisx, PlayState* play) { + EnSnowball* this = (EnSnowball*)thisx; + + Actor_ProcessInitChain(&this->actor, sInitChain); + EnSnowball_InitCollider(this, play); + CollisionCheck_SetInfo(&this->actor.colChkInfo, NULL, &sColChkInfoInit); + ActorShape_Init(&this->actor.shape, 595.0f, ActorShadow_DrawCircle, 9.4f); + this->actor.shape.shadowAlpha = 200; + + if (thisx->params == 1) { + this->actor.speedXZ += 5.0f; + this->actor.world.rot.y = Rand_ZeroFloat(65536.0f); + } +} + +void EnSnowball_Destroy(Actor* thisx, PlayState* play) { + EnSnowball* this = (EnSnowball*)thisx; + + Collider_DestroyJntSph(play, &this->collider); +} + +void EnSnowball_Update(Actor* thisx, PlayState* play) { + EnSnowball* this = (EnSnowball*)thisx; + Actor* player = GET_PLAYER(play); + + // Kill the actor if it falls too far + if (thisx->world.pos.y < -10000.0f) { + Actor_Kill(thisx); + return; + } + + u8 meanBoulder = thisx->params == 1 && this->actor.scale.x > 0.1f; + + // Check if the player is close enough to start rolling + if (this->actor.xzDistToPlayer < MAX(20.0f, this->actor.scale.x * 600.0f) && !meanBoulder) { + /// Flip the actor's rotation away from the player + thisx->world.rot.y = thisx->yawTowardsPlayer + 0x8000; + this->actor.speedXZ = MAX(5.0f, this->actor.speedXZ); + } + + if (this->collider.base.atFlags & AT_HIT) { + this->collider.base.atFlags &= ~AT_HIT; + // Flip the actor's rotation away from the player + thisx->world.rot.y = thisx->yawTowardsPlayer + 0x8000; + + func_8002F6D4(play, &this->actor, 2.0f, this->actor.yawTowardsPlayer, 0.0f, 0); + Player_PlaySfx(&GET_PLAYER(play)->actor, NA_SE_PL_BODY_HIT); + } + + // Slow down the actor and increase it's scale + if (this->actor.speedXZ > 0.0f) { + CollisionPoly snowballPoly; + u8 goingUp = this->actor.world.pos.y - this->prevY > 0.001f; + u8 goingDown = this->actor.world.pos.y - this->prevY < -0.001f; + + // friction + if (thisx->params != 1) { + this->actor.speedXZ -= 0.1f; + } + + if (goingDown) { + // Increase the speed if going down hill + f32 speed = (this->prevY - this->actor.world.pos.y) * 0.15f; + this->actor.speedXZ += MIN(speed, 0.5f); + } else if (goingUp) { + // Reduce the speed if going up hill + this->actor.speedXZ -= (this->actor.world.pos.y - this->prevY) * 0.1f; + } + + if (goingUp || goingDown) { + // Check if going straight, one degree right, or one degree left will result in steeper slope + // Check straight + Vec3f snowballPos = this->actor.world.pos; + snowballPos.y += 100.0f; + snowballPos.x += Math_SinS(this->actor.world.rot.y) * 1.0f; + snowballPos.z += Math_CosS(this->actor.world.rot.y) * 1.0f; + float straightSlope = BgCheck_AnyRaycastFloor1(&gPlayState->colCtx, &snowballPoly, &snowballPos); + + // Check one degree right + snowballPos = this->actor.world.pos; + snowballPos.y += 100.0f; + snowballPos.x += Math_SinS(this->actor.world.rot.y + 0x100) * 1.0f; + snowballPos.z += Math_CosS(this->actor.world.rot.y + 0x100) * 1.0f; + float rightSlope = BgCheck_AnyRaycastFloor1(&gPlayState->colCtx, &snowballPoly, &snowballPos); + + // Check one degree left + snowballPos = this->actor.world.pos; + snowballPos.y += 100.0f; + snowballPos.x += Math_SinS(this->actor.world.rot.y - 0x100) * 1.0f; + snowballPos.z += Math_CosS(this->actor.world.rot.y - 0x100) * 1.0f; + float leftSlope = BgCheck_AnyRaycastFloor1(&gPlayState->colCtx, &snowballPoly, &snowballPos); + + if (straightSlope > rightSlope || straightSlope > leftSlope) { + if (rightSlope < leftSlope) { + this->actor.world.rot.y += 0x100; + } else { + this->actor.world.rot.y -= 0x100; + } + } + } + + // Check if the actor is colliding with a wall and bounce off + if (thisx->bgCheckFlags & 8) { + if (ABS((s16)(thisx->wallYaw - thisx->world.rot.y)) > 0x4000) { + thisx->world.rot.y = ((thisx->wallYaw - thisx->world.rot.y) + thisx->wallYaw) - 0x8000; + } + if (thisx->params != 1) { + thisx->speedXZ *= 0.7f; + } + thisx->bgCheckFlags &= ~8; + if (this->actor.speedXZ > 5.0f) { + Audio_PlayActorSound2(thisx, NA_SE_EV_BOMB_BOUND); + } + } + + Actor_SetScale(&this->actor, MIN(0.15f, this->actor.scale.x + (this->actor.speedXZ * 0.00001f))); + } + + if (this->actor.speedXZ < 0.0f) { + this->actor.speedXZ = 0.0f; + } + + // Based on speed and scale, rotate the snowball + // The larger the snowball, the slower it rotates + this->sRot += (this->actor.speedXZ * 15.0f) / this->actor.scale.x; + + // record the actor's position + this->prevY = this->actor.world.pos.y; + + // Process movement (moves foward based on speed and rotation) + Actor_MoveXZGravity(thisx); + + // Prevent actor from going through the ground or walls + Actor_UpdateBgCheckInfo(play, &this->actor, MAX(10.0f, this->actor.scale.x * 250.0f), + MAX(10.0f, this->actor.scale.x * 500.0f), 0.0f, 0xFF); + + EnSnowball_UpdateCollider(this); + // Add collision checks if the actor is a mean boulder + if (meanBoulder) { + CollisionCheck_SetAT(play, &play->colChkCtx, &this->collider.base); + CollisionCheck_SetOC(play, &play->colChkCtx, &this->collider.base); + } +} + +void EnSnowball_Draw(Actor* thisx, PlayState* play) { + EnSnowball* this = (EnSnowball*)thisx; + + OPEN_DISPS(play->state.gfxCtx); + + Gfx_SetupDL_25Opa(play->state.gfxCtx); + + Matrix_RotateZYX(this->sRot, thisx->world.rot.y, 0, MTXMODE_APPLY); + + Matrix_Translate(0.0f, 5.0f, 0.0f, MTXMODE_APPLY); + Matrix_Scale(0.5f, 0.5f, 0.5f, MTXMODE_APPLY); + gSPMatrix(POLY_OPA_DISP++, MATRIX_NEWMTX(play->state.gfxCtx), G_MTX_NOPUSH | G_MTX_LOAD | G_MTX_MODELVIEW); + + gSPDisplayList(POLY_OPA_DISP++, gSnowballDL); + + CLOSE_DISPS(play->state.gfxCtx); +} diff --git a/soh/src/overlays/actors/ovl_En_Snowball/z_en_snowball.h b/soh/src/overlays/actors/ovl_En_Snowball/z_en_snowball.h new file mode 100644 index 00000000000..fa8c0779cfb --- /dev/null +++ b/soh/src/overlays/actors/ovl_En_Snowball/z_en_snowball.h @@ -0,0 +1,31 @@ +#ifndef Z_EN_SNOWBALL_H +#define Z_EN_SNOWBALL_H + +#include +#include "global.h" + +struct EnSnowball; + +typedef void (*EnSnowballActionFunc)(struct EnSnowball*, PlayState*); + +typedef struct EnSnowball { + Actor actor; + + ColliderJntSph collider; + ColliderJntSphElement colliderItems[1]; + s16 sRot; + f32 prevY; +} EnSnowball; + +#ifdef __cplusplus +extern "C" { +#endif +void EnSnowball_Init(Actor* thisx, PlayState* play); +void EnSnowball_Destroy(Actor* thisx, PlayState* play); +void EnSnowball_Update(Actor* thisx, PlayState* play); +void EnSnowball_Draw(Actor* thisx, PlayState* play); +#ifdef __cplusplus +} +#endif + +#endif \ No newline at end of file diff --git a/soh/src/overlays/actors/ovl_En_Sth/z_en_sth.c b/soh/src/overlays/actors/ovl_En_Sth/z_en_sth.c index 1cbc8c5efc4..a92571e41e0 100644 --- a/soh/src/overlays/actors/ovl_En_Sth/z_en_sth.c +++ b/soh/src/overlays/actors/ovl_En_Sth/z_en_sth.c @@ -12,6 +12,7 @@ #include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h" #include "soh/OTRGlobals.h" #include "soh/ResourceManagerHelpers.h" +#include "soh_assets.h" #define FLAGS (ACTOR_FLAG_ATTENTION_ENABLED | ACTOR_FLAG_FRIENDLY | ACTOR_FLAG_UPDATE_CULLING_DISABLED) @@ -398,6 +399,19 @@ void EnSth_PostLimbDraw(PlayState* play, s32 limbIndex, Gfx** dList, Vec3s* rot, CLOSE_DISPS(play->state.gfxCtx); } } + + if (limbIndex == 15 && CVarGetInteger("gHoliday.Visual.Hats", 0)) { + OPEN_DISPS(play->state.gfxCtx); + Matrix_Push(); + Matrix_RotateZYX(-4207, -665, -4650, MTXMODE_APPLY); + Matrix_Translate(932.432f, 162.163f, 81.082f, MTXMODE_APPLY); + Matrix_Scale(0.73f, 0.73f, 0.73f, MTXMODE_APPLY); + gDPSetEnvColor(POLY_OPA_DISP++, 255, 255, 0, 255); + gSPMatrix(POLY_OPA_DISP++, MATRIX_NEWMTX(play->state.gfxCtx), G_MTX_NOPUSH | G_MTX_LOAD | G_MTX_MODELVIEW); + gSPDisplayList(POLY_OPA_DISP++, gPaperCrownGenericDL); + Matrix_Pop(); + CLOSE_DISPS(play->state.gfxCtx); + } } Gfx* EnSth_AllocColorDList(GraphicsContext* play, u8 envR, u8 envG, u8 envB, u8 envA) { diff --git a/soh/src/overlays/actors/ovl_En_Syateki_Man/z_en_syateki_man.c b/soh/src/overlays/actors/ovl_En_Syateki_Man/z_en_syateki_man.c index 01052b9bb66..82512508853 100644 --- a/soh/src/overlays/actors/ovl_En_Syateki_Man/z_en_syateki_man.c +++ b/soh/src/overlays/actors/ovl_En_Syateki_Man/z_en_syateki_man.c @@ -7,6 +7,7 @@ #include "soh/OTRGlobals.h" #include "soh/ResourceManagerHelpers.h" #include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h" +#include "soh_assets.h" #define FLAGS \ (ACTOR_FLAG_ATTENTION_ENABLED | ACTOR_FLAG_FRIENDLY | ACTOR_FLAG_UPDATE_CULLING_DISABLED | \ @@ -521,12 +522,31 @@ s32 EnSyatekiMan_OverrideLimbDraw(PlayState* play, s32 limbIndex, Gfx** dList, V return 0; } +s32 EnSyatekiMan_PostLimbDraw(PlayState* play, s32 limbIndex, Gfx** dList, Vec3s* rot, void* thisx) { + EnSyatekiMan* this = (EnSyatekiMan*)thisx; + + if (limbIndex == 8 && CVarGetInteger("gHoliday.Visual.Hats", 0)) { + OPEN_DISPS(play->state.gfxCtx); + Matrix_Push(); + Matrix_RotateZYX(2214, 3985, -7750, MTXMODE_APPLY); + Matrix_Translate(1094.594f, 1162.162f, -40.541f, MTXMODE_APPLY); + Matrix_Scale(0.864f, 0.864f, 0.864f, MTXMODE_APPLY); + gDPSetEnvColor(POLY_OPA_DISP++, 0, 255, 0, 255); + gSPMatrix(POLY_OPA_DISP++, MATRIX_NEWMTX(play->state.gfxCtx), G_MTX_NOPUSH | G_MTX_LOAD | G_MTX_MODELVIEW); + gSPDisplayList(POLY_OPA_DISP++, gPaperCrownGenericDL); + Matrix_Pop(); + CLOSE_DISPS(play->state.gfxCtx); + } + + return false; +} + void EnSyatekiMan_Draw(Actor* thisx, PlayState* play) { s32 pad; EnSyatekiMan* this = (EnSyatekiMan*)thisx; Gfx_SetupDL_25Opa(play->state.gfxCtx); - SkelAnime_DrawSkeletonOpa(play, &this->skelAnime, EnSyatekiMan_OverrideLimbDraw, NULL, this); + SkelAnime_DrawSkeletonOpa(play, &this->skelAnime, EnSyatekiMan_OverrideLimbDraw, EnSyatekiMan_PostLimbDraw, this); } void EnSyatekiMan_SetBgm(void) { diff --git a/soh/src/overlays/actors/ovl_En_Ta/z_en_ta.c b/soh/src/overlays/actors/ovl_En_Ta/z_en_ta.c index a01d18f9802..23ab6219e90 100644 --- a/soh/src/overlays/actors/ovl_En_Ta/z_en_ta.c +++ b/soh/src/overlays/actors/ovl_En_Ta/z_en_ta.c @@ -10,6 +10,7 @@ #include "soh/OTRGlobals.h" #include "soh/ResourceManagerHelpers.h" #include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h" +#include "soh_assets.h" #define FLAGS (ACTOR_FLAG_ATTENTION_ENABLED | ACTOR_FLAG_FRIENDLY) @@ -1215,6 +1216,18 @@ void EnTa_PostLimbDraw(PlayState* play, s32 limbIndex, Gfx** dList, Vec3s* rot, if (limbIndex == 15) { Matrix_MultVec3f(&D_80B16E7C, &this->actor.focus.pos); } + + if (limbIndex == 15 && CVarGetInteger("gHoliday.Visual.Hats", 0)) { + OPEN_DISPS(play->state.gfxCtx); + Matrix_Push(); + Matrix_RotateZYX(11955, -1993, 221, MTXMODE_APPLY); + Matrix_Translate(1081.081f, -108.108f, -270.270f, MTXMODE_APPLY); + Matrix_Scale(1.554f, 1.554f, 1.554f, MTXMODE_APPLY); + gSPMatrix(POLY_OPA_DISP++, MATRIX_NEWMTX(play->state.gfxCtx), G_MTX_NOPUSH | G_MTX_LOAD | G_MTX_MODELVIEW); + gSPDisplayList(POLY_OPA_DISP++, gSantaHatGenericDL); + Matrix_Pop(); + CLOSE_DISPS(play->state.gfxCtx); + } } void EnTa_Draw(Actor* thisx, PlayState* play) { diff --git a/soh/src/overlays/actors/ovl_En_Test/z_en_test.c b/soh/src/overlays/actors/ovl_En_Test/z_en_test.c index 0bcf45a8b54..e41f843e19a 100644 --- a/soh/src/overlays/actors/ovl_En_Test/z_en_test.c +++ b/soh/src/overlays/actors/ovl_En_Test/z_en_test.c @@ -8,6 +8,9 @@ #include "objects/object_sk2/object_sk2.h" #include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h" #include "soh/ResourceManagerHelpers.h" +#include "soh_assets.h" + +#include "soh/Enhancements/Holiday/Archez.h" #define FLAGS (ACTOR_FLAG_ATTENTION_ENABLED | ACTOR_FLAG_HOSTILE | ACTOR_FLAG_UPDATE_CULLING_DISABLED) @@ -1844,6 +1847,10 @@ s32 EnTest_OverrideLimbDraw(PlayState* play, s32 limbIndex, Gfx** dList, Vec3f* *dList = NULL; } + if (limbIndex == STALFOS_LIMB_SWORD || limbIndex == STALFOS_LIMB_SHIELD) { + SkipOverrideNextLimb(); + } + return false; } @@ -1950,6 +1957,18 @@ void EnTest_PostLimbDraw(PlayState* play, s32 limbIndex, Gfx** dList, Vec3s* rot this->bodyPartsPos[bodyPart].z = sp50.z; } } + + if (limbIndex == 11 && CVarGetInteger("gHoliday.Visual.Hats", 0)) { + OPEN_DISPS(play->state.gfxCtx); + Matrix_Push(); + Matrix_RotateZYX(-10849, 0, -5314, MTXMODE_APPLY); + Matrix_Translate(513.514f, 283.784f, 554.054f, MTXMODE_APPLY); + Matrix_Scale(1.203f, 1.203f, 1.203f, MTXMODE_APPLY); + gSPMatrix(POLY_OPA_DISP++, MATRIX_NEWMTX(play->state.gfxCtx), G_MTX_NOPUSH | G_MTX_LOAD | G_MTX_MODELVIEW); + gSPDisplayList(POLY_OPA_DISP++, gSantaHatGenericDL); + Matrix_Pop(); + CLOSE_DISPS(play->state.gfxCtx); + } } void EnTest_Draw(Actor* thisx, PlayState* play) { diff --git a/soh/src/overlays/actors/ovl_En_Tg/z_en_tg.c b/soh/src/overlays/actors/ovl_En_Tg/z_en_tg.c index 40015812a2e..a0b4c17a451 100644 --- a/soh/src/overlays/actors/ovl_En_Tg/z_en_tg.c +++ b/soh/src/overlays/actors/ovl_En_Tg/z_en_tg.c @@ -6,6 +6,7 @@ #include "z_en_tg.h" #include "objects/object_mu/object_mu.h" +#include "soh_assets.h" #define FLAGS (ACTOR_FLAG_ATTENTION_ENABLED | ACTOR_FLAG_FRIENDLY) @@ -167,6 +168,29 @@ void EnTg_PostLimbDraw(PlayState* play, s32 limbIndex, Gfx** dList, Vec3s* rot, if (limbIndex == 9) { // Place the target point at the guy's head instead of the center of the actor Matrix_MultVec3f(&targetOffset, &this->actor.focus.pos); + if (CVarGetInteger("gHoliday.Visual.Hats", 0)) { + OPEN_DISPS(play->state.gfxCtx); + Matrix_Push(); + Matrix_RotateZYX(-3100, 1992, 2435, MTXMODE_APPLY); + Matrix_Translate(864.865f, -121.622f, 175.676f, MTXMODE_APPLY); + Matrix_Scale(0.865f, 0.865f, 0.865f, MTXMODE_APPLY); + gDPSetEnvColor(POLY_OPA_DISP++, 255, 0, 255, 255); + gSPMatrix(POLY_OPA_DISP++, MATRIX_NEWMTX(play->state.gfxCtx), G_MTX_NOPUSH | G_MTX_LOAD | G_MTX_MODELVIEW); + gSPDisplayList(POLY_OPA_DISP++, gPaperCrownGenericDL); + Matrix_Pop(); + CLOSE_DISPS(play->state.gfxCtx); + } + } else if (limbIndex == 20 && CVarGetInteger("gHoliday.Visual.Hats", 0)) { + OPEN_DISPS(play->state.gfxCtx); + Matrix_Push(); + Matrix_RotateZYX(-2657, -1550, 1549, MTXMODE_APPLY); + Matrix_Translate(594.594f, -135.135f, -54.054f, MTXMODE_APPLY); + Matrix_Scale(0.966f, 0.966f, 0.966f, MTXMODE_APPLY); + gDPSetEnvColor(POLY_OPA_DISP++, 0, 0, 255, 255); + gSPMatrix(POLY_OPA_DISP++, MATRIX_NEWMTX(play->state.gfxCtx), G_MTX_NOPUSH | G_MTX_LOAD | G_MTX_MODELVIEW); + gSPDisplayList(POLY_OPA_DISP++, gPaperCrownGenericDL); + Matrix_Pop(); + CLOSE_DISPS(play->state.gfxCtx); } } diff --git a/soh/src/overlays/actors/ovl_En_Tk/z_en_tk.c b/soh/src/overlays/actors/ovl_En_Tk/z_en_tk.c index 96ce945e486..3f558df2a82 100644 --- a/soh/src/overlays/actors/ovl_En_Tk/z_en_tk.c +++ b/soh/src/overlays/actors/ovl_En_Tk/z_en_tk.c @@ -10,6 +10,7 @@ #include "soh/frame_interpolation.h" #include "soh/ResourceManagerHelpers.h" #include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h" +#include "soh_assets.h" #define FLAGS (ACTOR_FLAG_ATTENTION_ENABLED | ACTOR_FLAG_FRIENDLY) @@ -728,6 +729,18 @@ void EnTk_PostLimbDraw(PlayState* play, s32 limbIndex, Gfx** dList, Vec3s* rot, Matrix_MultVec3f(&sp28, &this->v3f_304); func_80B1D200(play); } + + if (limbIndex == 16 && CVarGetInteger("gHoliday.Visual.Hats", 0)) { + OPEN_DISPS(play->state.gfxCtx); + Matrix_Push(); + Matrix_RotateZYX(10627, 3321, -13727, MTXMODE_APPLY); + Matrix_Translate(418.919f, 40.54f, -256.757f, MTXMODE_APPLY); + Matrix_Scale(1.068f, 1.068f, 1.068f, MTXMODE_APPLY); + gSPMatrix(POLY_OPA_DISP++, MATRIX_NEWMTX(play->state.gfxCtx), G_MTX_NOPUSH | G_MTX_LOAD | G_MTX_MODELVIEW); + gSPDisplayList(POLY_OPA_DISP++, gSantaHatGenericDL); + Matrix_Pop(); + CLOSE_DISPS(play->state.gfxCtx); + } } void EnTk_Draw(Actor* thisx, PlayState* play) { diff --git a/soh/src/overlays/actors/ovl_En_Torch2/z_en_torch2.c b/soh/src/overlays/actors/ovl_En_Torch2/z_en_torch2.c index ceca597df8b..241073b228a 100644 --- a/soh/src/overlays/actors/ovl_En_Torch2/z_en_torch2.c +++ b/soh/src/overlays/actors/ovl_En_Torch2/z_en_torch2.c @@ -8,6 +8,8 @@ #include "objects/object_torch2/object_torch2.h" #include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h" +#include "soh/Enhancements/Holiday/Archez.h" + #define FLAGS \ (ACTOR_FLAG_ATTENTION_ENABLED | ACTOR_FLAG_HOSTILE | ACTOR_FLAG_UPDATE_CULLING_DISABLED | \ ACTOR_FLAG_DRAW_CULLING_DISABLED) @@ -780,6 +782,10 @@ s32 EnTorch2_OverrideLimbDraw(PlayState* play, s32 limbIndex, Gfx** dList, Vec3f Gfx** gfx) { Player* this = (Player*)thisx; + if (limbIndex == PLAYER_LIMB_L_HAND || limbIndex == PLAYER_LIMB_R_HAND) { + SkipOverrideNextLimb(); + } + return Player_OverrideLimbDrawGameplayCommon(play, limbIndex, dList, pos, rot, &this->actor); } diff --git a/soh/src/overlays/actors/ovl_En_Toryo/z_en_toryo.c b/soh/src/overlays/actors/ovl_En_Toryo/z_en_toryo.c index e67277d22ff..f1466dd5695 100644 --- a/soh/src/overlays/actors/ovl_En_Toryo/z_en_toryo.c +++ b/soh/src/overlays/actors/ovl_En_Toryo/z_en_toryo.c @@ -9,6 +9,7 @@ #include "soh/OTRGlobals.h" #include "soh/ResourceManagerHelpers.h" #include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h" +#include "soh_assets.h" #define FLAGS (ACTOR_FLAG_ATTENTION_ENABLED | ACTOR_FLAG_FRIENDLY) @@ -415,4 +416,16 @@ void EnToryo_PostLimbDraw(PlayState* play, s32 limbIndex, Gfx** dList, Vec3s* ro Matrix_MultVec3f(&sMultVec, &this->actor.focus.pos); break; } + + if (limbIndex == 15 && CVarGetInteger("gHoliday.Visual.Hats", 0)) { + OPEN_DISPS(play->state.gfxCtx); + Matrix_Push(); + Matrix_RotateZYX(-23691, 664, -2879, MTXMODE_APPLY); + Matrix_Translate(810.811f, -243.243f, 270.27f, MTXMODE_APPLY); + Matrix_Scale(1.216f, 1.216f, 1.216f, MTXMODE_APPLY); + gSPMatrix(POLY_OPA_DISP++, MATRIX_NEWMTX(play->state.gfxCtx), G_MTX_NOPUSH | G_MTX_LOAD | G_MTX_MODELVIEW); + gSPDisplayList(POLY_OPA_DISP++, gSantaHatGenericDL); + Matrix_Pop(); + CLOSE_DISPS(play->state.gfxCtx); + } } diff --git a/soh/src/overlays/actors/ovl_En_Vm/z_en_vm.c b/soh/src/overlays/actors/ovl_En_Vm/z_en_vm.c index a26e3eab97e..db8c33deed7 100644 --- a/soh/src/overlays/actors/ovl_En_Vm/z_en_vm.c +++ b/soh/src/overlays/actors/ovl_En_Vm/z_en_vm.c @@ -10,6 +10,9 @@ #include "objects/gameplay_keep/gameplay_keep.h" #include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h" #include "soh/ResourceManagerHelpers.h" +#include "soh_assets.h" + +#include "soh/Enhancements/Holiday/Archez.h" #define FLAGS (ACTOR_FLAG_ATTENTION_ENABLED | ACTOR_FLAG_UPDATE_CULLING_DISABLED) @@ -519,6 +522,18 @@ void EnVm_PostLimbDraw(PlayState* play, s32 limbIndex, Gfx** dList, Vec3s* rot, &this->colliderQuad2.dim.quad[1], &this->colliderQuad2.dim.quad[2], &this->colliderQuad2.dim.quad[3]); } + + if (limbIndex == 6 && CVarGetInteger("gHoliday.Visual.Hats", 0)) { + OPEN_DISPS(play->state.gfxCtx); + Matrix_Push(); + Matrix_RotateZYX(19704, -1329, 11734, MTXMODE_APPLY); + Matrix_Translate(310.811f, -108.108f, -81.081f, MTXMODE_APPLY); + Matrix_Scale(2.297f, 2.297f, 2.297f, MTXMODE_APPLY); + gSPMatrix(POLY_OPA_DISP++, MATRIX_NEWMTX(play->state.gfxCtx), G_MTX_NOPUSH | G_MTX_LOAD | G_MTX_MODELVIEW); + gSPDisplayList(POLY_OPA_DISP++, gSantaHatGenericDL); + Matrix_Pop(); + CLOSE_DISPS(play->state.gfxCtx); + } } void EnVm_Draw(Actor* thisx, PlayState* play2) { @@ -530,6 +545,7 @@ void EnVm_Draw(Actor* thisx, PlayState* play2) { Gfx_SetupDL_25Opa(play->state.gfxCtx); Gfx_SetupDL_25Xlu(play->state.gfxCtx); + SkipOverrideNextSkeleton(); SkelAnime_DrawSkeletonOpa(play, &this->skelAnime, EnVm_OverrideLimbDraw, EnVm_PostLimbDraw, this); actorPos = this->actor.world.pos; func_80033C30(&actorPos, &D_80B2EB7C, 255, play); diff --git a/soh/src/overlays/actors/ovl_En_Weather_Tag/z_en_weather_tag.c b/soh/src/overlays/actors/ovl_En_Weather_Tag/z_en_weather_tag.c index 084334a6b06..ad635316c78 100644 --- a/soh/src/overlays/actors/ovl_En_Weather_Tag/z_en_weather_tag.c +++ b/soh/src/overlays/actors/ovl_En_Weather_Tag/z_en_weather_tag.c @@ -136,6 +136,11 @@ u8 WeatherTag_CheckEnableWeatherEffect(EnWeatherTag* this, PlayState* play, u8 a u8 ret = false; Player* player = GET_PLAYER(play); + if (LINK_IS_ADULT && gPlayState != NULL && gPlayState->sceneNum == SCENE_KAKARIKO_VILLAGE && + CVAR_GENERAL("LetItSnow")) { + return ret; + } + if (Actor_WorldDistXZToActor(&player->actor, &this->actor) < WEATHER_TAG_RANGE100(this->actor.params)) { if ((play->envCtx.indoors != 0) || !gSkyboxBlendingEnabled || (play->skyboxId != SKYBOX_NORMAL_SKY && play->envCtx.unk_1F == play->envCtx.unk_20)) { diff --git a/soh/src/overlays/actors/ovl_En_Wf/z_en_wf.c b/soh/src/overlays/actors/ovl_En_Wf/z_en_wf.c index 48f17149e84..bf6916d512b 100644 --- a/soh/src/overlays/actors/ovl_En_Wf/z_en_wf.c +++ b/soh/src/overlays/actors/ovl_En_Wf/z_en_wf.c @@ -10,6 +10,7 @@ #include "objects/object_wf/object_wf.h" #include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h" #include "soh/ResourceManagerHelpers.h" +#include "soh_assets.h" #define FLAGS (ACTOR_FLAG_ATTENTION_ENABLED | ACTOR_FLAG_HOSTILE | ACTOR_FLAG_UPDATE_CULLING_DISABLED) @@ -1428,6 +1429,19 @@ void EnWf_PostLimbDraw(PlayState* play, s32 limbIndex, Gfx** dList, Vec3s* rot, this->bodyPartsPos[bodyPartIndex].z = bodyPartPos.z; } } + + if (limbIndex == 17 && CVarGetInteger("gHoliday.Visual.Hats", 0)) { + OPEN_DISPS(play->state.gfxCtx); + Matrix_Push(); + Matrix_RotateZYX(0, 0, -18377, MTXMODE_APPLY); + Matrix_Translate(729.73f, 1243.243f, 0.0f, MTXMODE_APPLY); + Matrix_Scale(0.743f, 0.743f, 0.743f, MTXMODE_APPLY); + gDPSetEnvColor(POLY_OPA_DISP++, 255, 0, 0, 255); + gSPMatrix(POLY_OPA_DISP++, MATRIX_NEWMTX(play->state.gfxCtx), G_MTX_NOPUSH | G_MTX_LOAD | G_MTX_MODELVIEW); + gSPDisplayList(POLY_OPA_DISP++, gPaperCrownGenericDL); + Matrix_Pop(); + CLOSE_DISPS(play->state.gfxCtx); + } } static void* sWolfosNormalEyeTextures[] = { gWolfosNormalEyeOpenTex, gWolfosNormalEyeHalfTex, gWolfosNormalEyeNarrowTex, diff --git a/soh/src/overlays/actors/ovl_En_Wood02/z_en_wood02.c b/soh/src/overlays/actors/ovl_En_Wood02/z_en_wood02.c index 1fe96d38c67..4fd14130d65 100644 --- a/soh/src/overlays/actors/ovl_En_Wood02/z_en_wood02.c +++ b/soh/src/overlays/actors/ovl_En_Wood02/z_en_wood02.c @@ -7,6 +7,7 @@ #include "z_en_wood02.h" #include "objects/object_wood02/object_wood02.h" #include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h" +#include "soh/Enhancements/Holiday/Fredomato.h" #define FLAGS 0 @@ -356,8 +357,9 @@ void EnWood02_Update(Actor* thisx, PlayState* play2) { if (this->actor.home.rot.y != 0) { dropsSpawnPt = this->actor.world.pos; dropsSpawnPt.y += 200.0f; - - if (GameInteractor_Should(VB_TREE_DROP_ITEM, true, this)) { + if (HandleTreeBonk(&this->actor)) { + // no-op + } else if (GameInteractor_Should(VB_TREE_DROP_ITEM, true, this)) { if ((this->unk_14C >= 0) && (this->unk_14C < 0x64)) { if (GameInteractor_Should(VB_TREE_DROP_COLLECTIBLE, true, this)) { Item_DropCollectibleRandom(play, &this->actor, &dropsSpawnPt, this->unk_14C << 4); @@ -458,6 +460,10 @@ void EnWood02_Draw(Actor* thisx, PlayState* play) { red = green = blue = 255; } + if (CVarGetInteger("gHoliday.Visual.SnowingWeather", 0)) { + red = green = blue = 255; + } + Gfx_SetupDL_25Xlu(gfxCtx); if (GameInteractor_Should(VB_TREE_SETUP_DRAW, (this->actor.params == WOOD_LEAF_GREEN) || (this->actor.params == WOOD_LEAF_YELLOW), diff --git a/soh/src/overlays/actors/ovl_En_Zf/z_en_zf.c b/soh/src/overlays/actors/ovl_En_Zf/z_en_zf.c index 99d9fba48a6..b213d511445 100644 --- a/soh/src/overlays/actors/ovl_En_Zf/z_en_zf.c +++ b/soh/src/overlays/actors/ovl_En_Zf/z_en_zf.c @@ -9,6 +9,8 @@ #include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h" #include "soh/ResourceManagerHelpers.h" +#include "soh/Enhancements/Holiday/Archez.h" + #define FLAGS (ACTOR_FLAG_ATTENTION_ENABLED | ACTOR_FLAG_HOSTILE | ACTOR_FLAG_UPDATE_CULLING_DISABLED) void EnZf_Init(Actor* thisx, PlayState* play); @@ -2130,11 +2132,13 @@ s32 EnZf_OverrideLimbDraw(PlayState* play, s32 limbIndex, Gfx** dList, Vec3f* po rot->y -= this->headRot; break; case ENZF_LIMB_SWORD: + SkipOverrideNextLimb(); if (this->swordSheathed) { *dList = gZfEmptyHandDL; } break; case ENZF_LIMB_SCABBARD: + SkipOverrideNextLimb(); if (this->swordSheathed) { *dList = gZfSheathedSwordDL; } @@ -2431,4 +2435,4 @@ s32 EnZf_DodgeRangedWaiting(PlayState* play, EnZf* this) { void EnZf_Reset(void) { D_80B4A1B0 = 0; D_80B4A1B4 = 1; -} \ No newline at end of file +} diff --git a/soh/src/overlays/actors/ovl_End_Title/z_end_title.c b/soh/src/overlays/actors/ovl_End_Title/z_end_title.c index b87e127ce20..a32eae631c3 100644 --- a/soh/src/overlays/actors/ovl_End_Title/z_end_title.c +++ b/soh/src/overlays/actors/ovl_End_Title/z_end_title.c @@ -72,41 +72,83 @@ void EndTitle_DrawFull(Actor* thisx, PlayState* play) { OPEN_DISPS(play->state.gfxCtx); + uint8_t isKak = play->sceneNum == SCENE_KAKARIKO_VILLAGE; + // Draw title cards on the screen - if ((frameCount > 890) && (this->endAlpha < 200)) { + if ((frameCount > 890 || isKak) && (this->endAlpha < 200)) { this->endAlpha += 7; } - if ((frameCount > 810) && (this->tlozAlpha < 200)) { + if ((frameCount > 810 || isKak) && (this->tlozAlpha < 200)) { this->tlozAlpha += 15; } - if ((frameCount > 850) && (this->ootAlpha < 200)) { + if ((frameCount > 850 || isKak) && (this->ootAlpha < 200)) { this->ootAlpha += 15; } OVERLAY_DISP = Gfx_SetupDL_64(OVERLAY_DISP); - gDPSetTextureLUT(OVERLAY_DISP++, G_TT_NONE); - gDPSetEnvColor(OVERLAY_DISP++, 255, 120, 30, 0); - gDPSetRenderMode(OVERLAY_DISP++, G_RM_PASS, G_RM_XLU_SURF2); - gSPClearGeometryMode(OVERLAY_DISP++, - G_TEXTURE_ENABLE | G_CULL_BACK | G_FOG | G_LIGHTING | G_TEXTURE_GEN | G_TEXTURE_GEN_LINEAR); - gDPSetCombineLERP(OVERLAY_DISP++, PRIMITIVE, ENVIRONMENT, TEXEL0, ENVIRONMENT, TEXEL0, 0, PRIMITIVE, 0, 0, 0, 0, - COMBINED, 0, 0, 0, COMBINED); - gDPSetPrimColor(OVERLAY_DISP++, 0x00, 0x80, 0, 0, 0, this->endAlpha); - gDPLoadTextureTile(OVERLAY_DISP++, sTheEndTex, G_IM_FMT_IA, G_IM_SIZ_8b, 80, 24, 0, 0, 80 - 1, 24 - 1, 0, - G_TX_NOMIRROR | G_TX_WRAP, G_TX_NOMIRROR | G_TX_WRAP, 0, 0, 0, 0); - gSPTextureRectangle(OVERLAY_DISP++, 120 << 2, 90 << 2, 200 << 2, 113 << 2, G_TX_RENDERTILE, 0, 0, 1 << 10, 1 << 10); - gDPPipeSync(OVERLAY_DISP++); - gDPSetPrimColor(OVERLAY_DISP++, 0x00, 0x80, 0, 0, 0, this->tlozAlpha); - gDPLoadTextureTile(OVERLAY_DISP++, sTheLegendOfZeldaTex, G_IM_FMT_IA, G_IM_SIZ_8b, 120, 24, 0, 0, 120 - 1, 24 - 1, - 0, G_TX_NOMIRROR | G_TX_WRAP, G_TX_NOMIRROR | G_TX_WRAP, 0, 0, 0, 0); - gSPTextureRectangle(OVERLAY_DISP++, 100 << 2, 160 << 2, 220 << 2, 183 << 2, G_TX_RENDERTILE, 0, 0, 1 << 10, - 1 << 10); - gDPPipeSync(OVERLAY_DISP++); - gDPSetPrimColor(OVERLAY_DISP++, 0x00, 0x80, 0, 0, 0, this->ootAlpha); - gDPLoadTextureTile(OVERLAY_DISP++, sOcarinaOfTimeTex, G_IM_FMT_IA, G_IM_SIZ_8b, 112, 16, 0, 0, 112 - 1, 16 - 1, 0, - G_TX_NOMIRROR | G_TX_WRAP, G_TX_NOMIRROR | G_TX_WRAP, 0, 0, 0, 0); - gSPTextureRectangle(OVERLAY_DISP++, 104 << 2, 177 << 2, 216 << 2, 192 << 2, G_TX_RENDERTILE, 0, 0, 1 << 10, - 1 << 10); + + if (this->actor.params == 2) { + gSPGrayscale(OVERLAY_DISP++, false); + gDPSetTextureLUT(OVERLAY_DISP++, G_TT_NONE); + gDPSetEnvColor(OVERLAY_DISP++, 0, 255, 0, 0); + gDPSetRenderMode(OVERLAY_DISP++, G_RM_PASS, G_RM_XLU_SURF2); + gSPClearGeometryMode(OVERLAY_DISP++, G_TEXTURE_ENABLE | G_CULL_BACK | G_FOG | G_LIGHTING | G_TEXTURE_GEN | + G_TEXTURE_GEN_LINEAR); + gDPSetCombineLERP(OVERLAY_DISP++, PRIMITIVE, ENVIRONMENT, TEXEL0, ENVIRONMENT, TEXEL0, 0, PRIMITIVE, 0, 0, 0, 0, + COMBINED, 0, 0, 0, COMBINED); + gDPSetPrimColor(OVERLAY_DISP++, 0x00, 0x80, 0, 0, 0, this->endAlpha); + gDPLoadTextureTile(OVERLAY_DISP++, sTheEndTex, G_IM_FMT_IA, G_IM_SIZ_8b, 80, 24, 0, 0, 80 - 1, 24 - 1, 0, + G_TX_NOMIRROR | G_TX_WRAP, G_TX_NOMIRROR | G_TX_WRAP, 0, 0, 0, 0); + gSPTextureRectangle(OVERLAY_DISP++, 120 << 2, 90 << 2, 200 << 2, 113 << 2, G_TX_RENDERTILE, 0, 0, 1 << 10, + 1 << 10); + gDPPipeSync(OVERLAY_DISP++); + gDPSetPrimColor(OVERLAY_DISP++, 0x00, 0x80, 0, 0, 0, this->tlozAlpha); + gDPLoadTextureTile(OVERLAY_DISP++, sTheLegendOfZeldaTex, G_IM_FMT_IA, G_IM_SIZ_8b, 120, 24, 0, 0, 120 - 1, + 24 - 1, 0, G_TX_NOMIRROR | G_TX_WRAP, G_TX_NOMIRROR | G_TX_WRAP, 0, 0, 0, 0); + gSPTextureRectangle(OVERLAY_DISP++, 100 << 2, 160 << 2, 220 << 2, 183 << 2, G_TX_RENDERTILE, 0, 0, 1 << 10, + 1 << 10); + gDPPipeSync(OVERLAY_DISP++); + gDPSetPrimColor(OVERLAY_DISP++, 0x00, 0x80, 0, 0, 0, this->ootAlpha); + gDPLoadTextureTile(OVERLAY_DISP++, sOcarinaOfTimeTex, G_IM_FMT_IA, G_IM_SIZ_8b, 112, 16, 0, 0, 112 - 1, 16 - 1, + 0, G_TX_NOMIRROR | G_TX_WRAP, G_TX_NOMIRROR | G_TX_WRAP, 0, 0, 0, 0); + gSPTextureRectangle(OVERLAY_DISP++, 104 << 2, 177 << 2, 216 << 2, 192 << 2, G_TX_RENDERTILE, 0, 0, 1 << 10, + 1 << 10); + + gVisMonoColor.a = 0; + // D_801614B0.r = 205; + // D_801614B0.g = 168; + // D_801614B0.b = 130; + + // gSPGrayscale(OVERLAY_DISP++, true); + } else if (gVisMonoColor.a > 0) { + gSPGrayscale(OVERLAY_DISP++, false); + gDPSetTextureLUT(OVERLAY_DISP++, G_TT_NONE); + gDPSetEnvColor(OVERLAY_DISP++, 255, 120, 30, 0); + gDPSetRenderMode(OVERLAY_DISP++, G_RM_PASS, G_RM_XLU_SURF2); + gSPClearGeometryMode(OVERLAY_DISP++, G_TEXTURE_ENABLE | G_CULL_BACK | G_FOG | G_LIGHTING | G_TEXTURE_GEN | + G_TEXTURE_GEN_LINEAR); + gDPSetCombineLERP(OVERLAY_DISP++, PRIMITIVE, ENVIRONMENT, TEXEL0, ENVIRONMENT, TEXEL0, 0, PRIMITIVE, 0, 0, 0, 0, + COMBINED, 0, 0, 0, COMBINED); + gDPSetPrimColor(OVERLAY_DISP++, 0x00, 0x80, 0, 0, 0, this->endAlpha); + gDPLoadTextureTile(OVERLAY_DISP++, sTheEndTex, G_IM_FMT_IA, G_IM_SIZ_8b, 80, 24, 0, 0, 80 - 1, 24 - 1, 0, + G_TX_NOMIRROR | G_TX_WRAP, G_TX_NOMIRROR | G_TX_WRAP, 0, 0, 0, 0); + gSPTextureRectangle(OVERLAY_DISP++, 120 << 2, 90 << 2, 200 << 2, 113 << 2, G_TX_RENDERTILE, 0, 0, 1 << 10, + 1 << 10); + gDPPipeSync(OVERLAY_DISP++); + gDPSetPrimColor(OVERLAY_DISP++, 0x00, 0x80, 0, 0, 0, this->tlozAlpha); + gDPLoadTextureTile(OVERLAY_DISP++, sTheLegendOfZeldaTex, G_IM_FMT_IA, G_IM_SIZ_8b, 120, 24, 0, 0, 120 - 1, + 24 - 1, 0, G_TX_NOMIRROR | G_TX_WRAP, G_TX_NOMIRROR | G_TX_WRAP, 0, 0, 0, 0); + gSPTextureRectangle(OVERLAY_DISP++, 100 << 2, 160 << 2, 220 << 2, 183 << 2, G_TX_RENDERTILE, 0, 0, 1 << 10, + 1 << 10); + gDPPipeSync(OVERLAY_DISP++); + gDPSetPrimColor(OVERLAY_DISP++, 0x00, 0x80, 0, 0, 0, this->ootAlpha); + gDPLoadTextureTile(OVERLAY_DISP++, sOcarinaOfTimeTex, G_IM_FMT_IA, G_IM_SIZ_8b, 112, 16, 0, 0, 112 - 1, 16 - 1, + 0, G_TX_NOMIRROR | G_TX_WRAP, G_TX_NOMIRROR | G_TX_WRAP, 0, 0, 0, 0); + gSPTextureRectangle(OVERLAY_DISP++, 104 << 2, 177 << 2, 216 << 2, 192 << 2, G_TX_RENDERTILE, 0, 0, 1 << 10, + 1 << 10); + if (gVisMonoColor.a > 0) + gSPGrayscale(OVERLAY_DISP++, true); + } CLOSE_DISPS(play->state.gfxCtx); } diff --git a/soh/src/overlays/actors/ovl_player_actor/z_player.c b/soh/src/overlays/actors/ovl_player_actor/z_player.c index 2de7113d218..470ae4286e0 100644 --- a/soh/src/overlays/actors/ovl_player_actor/z_player.c +++ b/soh/src/overlays/actors/ovl_player_actor/z_player.c @@ -2570,8 +2570,10 @@ void Player_ProcessItemButtons(Player* this, PlayState* play) { sHeldItemButtonIsHeldDown = true; } } else if (GameInteractor_Should(VB_CHANGE_HELD_ITEM_AND_USE_ITEM, true, item)) { - this->heldItemButton = i; - Player_UseItem(play, this, item); + if (GameInteractor_Should(VB_USE_ITEM, true, &item)) { + this->heldItemButton = i; + Player_UseItem(play, this, item); + } } } } @@ -2692,9 +2694,13 @@ s32 func_8083442C(Player* this, PlayState* play) { magicArrowType = arrowType - ARROW_FIRE; if (this->unk_860 >= 0) { - if ((magicArrowType >= 0) && (magicArrowType <= 2) && - !Magic_RequestChange(play, sMagicArrowCosts[magicArrowType], MAGIC_CONSUME_NOW)) { - arrowType = ARROW_NORMAL; + if ((magicArrowType >= 0) && (magicArrowType <= 2)) { + if (GameInteractor_Should(VB_PLAYER_ARROW_MAGIC_CONSUMPTION, true, this, magicArrowType, + &arrowType)) { + if (!Magic_RequestChange(play, sMagicArrowCosts[magicArrowType], MAGIC_CONSUME_NOW)) { + arrowType = ARROW_NORMAL; + } + } } this->heldActor = Actor_SpawnAsChild( @@ -3555,6 +3561,7 @@ void func_80836448(PlayState* play, Player* this, LinkAnimationHeader* anim) { Audio_PlayFanfare(NA_BGM_GAME_OVER); gSaveContext.seqId = (u8)NA_BGM_DISABLED; gSaveContext.natureAmbienceId = NATURE_ID_DISABLED; + GameInteractor_ExecuteOnPlayerDeath(); } OnePointCutscene_Init(play, 9806, cond ? 120 : 60, &this->actor, MAIN_CAM); @@ -4450,6 +4457,7 @@ void func_80837948(PlayState* play, Player* this, s32 arg2) { if ((arg2 >= PLAYER_MWA_FLIPSLASH_START) && (arg2 <= PLAYER_MWA_JUMPSLASH_FINISH)) { if (CVarGetInteger(CVAR_GENERAL("RestoreQPA"), 1) && temp == -1) { dmgFlags = 0x16171617; + GameInteractor_ExecuteOnQPADamage(&dmgFlags); } else { dmgFlags = D_80854488[temp][1]; } @@ -5788,7 +5796,8 @@ void func_8083AA10(Player* this, PlayState* play) { if (!(this->stateFlags3 & PLAYER_STATE3_MIDAIR) && !(this->skelAnime.movementFlags & 0x80) && (Player_Action_8084411C != this->actionFunc) && (Player_Action_80844A44 != this->actionFunc)) { - if ((sPrevFloorProperty == 7) || (this->meleeWeaponState != 0)) { + if ((sPrevFloorProperty == 7) || + (GameInteractor_Should(VB_HOVER_WITH_ISG, true) && (this->meleeWeaponState != 0))) { Math_Vec3f_Copy(&this->actor.world.pos, &this->actor.prevPos); Player_ZeroSpeedXZ(this); return; @@ -6103,6 +6112,9 @@ s32 Player_ActionHandler_13(Player* this, PlayState* play) { func_80835EA4(play, 2); } } else { + if (GameInteractor_Should(VB_SKIP_FORCE_PLAY_OCARINA, false)) { + return 0; + } Player_SetupActionPreserveItemAction(play, this, Player_Action_8084E3C4, 0); Player_AnimPlayOnceAdjusted(play, this, &gPlayerAnim_link_normal_okarina_start); this->stateFlags2 |= PLAYER_STATE2_OCARINA_PLAYING; @@ -6301,11 +6313,16 @@ s32 func_8083BBA0(Player* this, PlayState* play) { } void Player_SetupRoll(Player* this, PlayState* play) { + if (!GameInteractor_Should(VB_PLAYER_ROLL, true)) { + return; + } + Player_SetupAction(play, this, Player_Action_Roll, 0); LinkAnimation_PlayOnceSetSpeed(play, &this->skelAnime, GET_PLAYER_ANIM(PLAYER_ANIMGROUP_landing_roll, this->modelAnimType), 1.25f * sWaterSpeedFactor); gSaveContext.ship.stats.count[COUNT_ROLLS]++; + GameInteractor_ExecuteOnPlayerRoll(); } s32 Player_TryRoll(Player* this, PlayState* play) { @@ -7127,9 +7144,13 @@ void func_8083DFE0(Player* this, f32* arg1, s16* arg2) { if (this->meleeWeaponState == 0) { float maxSpeed = R_RUN_SPEED_LIMIT / 100.0f; - if (CVarGetInteger(CVAR_ENHANCEMENT("MMBunnyHood"), BUNNY_HOOD_VANILLA) == BUNNY_HOOD_FAST_AND_JUMP && - this->currentMask == PLAYER_MASK_BUNNY) { - maxSpeed *= 1.5f; + if (this->currentMask == PLAYER_MASK_BUNNY) { + if (IS_ROGUELIKE) { + maxSpeed *= 1.0f + (((f32)gSaveContext.ship.quest.data.rogueLike.stats[RL_SPEED]) / 10.0f) * 1.5f; + } else if (CVarGetInteger(CVAR_ENHANCEMENT("MMBunnyHood"), BUNNY_HOOD_VANILLA) == + BUNNY_HOOD_FAST_AND_JUMP) { + maxSpeed *= 1.5f; + } } if (CVarGetInteger(CVAR_SETTING("WalkModifier.Enabled"), 0) && @@ -7141,9 +7162,12 @@ void func_8083DFE0(Player* this, f32* arg1, s16* arg2) { maxSpeed *= CVarGetFloat(CVAR_SETTING("WalkModifier.Mapping2"), 1.0f); } } else { - if (CHECK_BTN_ALL(sControlInput->cur.button, BTN_CUSTOM_MODIFIER1)) { + const s32 mod1Mask = CVarGetInteger(CVAR_SETTING("WalkModifier.Mod1Btn"), BTN_CUSTOM_MODIFIER1); + const s32 mod2Mask = CVarGetInteger(CVAR_SETTING("WalkModifier.Mod2Btn"), BTN_CUSTOM_MODIFIER2); + + if (mod1Mask != 0 && CHECK_BTN_ALL(sControlInput->cur.button, mod1Mask)) { maxSpeed *= CVarGetFloat(CVAR_SETTING("WalkModifier.Mapping1"), 1.0f); - } else if (CHECK_BTN_ALL(sControlInput->cur.button, BTN_CUSTOM_MODIFIER2)) { + } else if (mod2Mask != 0 && CHECK_BTN_ALL(sControlInput->cur.button, mod2Mask)) { maxSpeed *= CVarGetFloat(CVAR_SETTING("WalkModifier.Mapping2"), 1.0f); } } @@ -7339,7 +7363,7 @@ s32 Player_ActionHandler_2(Player* this, PlayState* play) { // Only skip cutscenes for drops when they're items/consumables from bushes/rocks/enemies. uint8_t isDropToSkip = (interactedActor->id == ACTOR_EN_ITEM00 && interactedActor->params != ITEM00_HEART_PIECE && - interactedActor->params != ITEM00_SMALL_KEY && + interactedActor->params != ITEM00_SMALL_KEY && interactedActor->params != ITEM00_NONE && interactedActor->params != ITEM00_SOH_GIVE_ITEM_ENTRY && interactedActor->params != ITEM00_SOH_GIVE_ITEM_ENTRY_GI) || interactedActor->id == ACTOR_EN_KAREBABA || interactedActor->id == ACTOR_EN_DEKUBABA; @@ -8373,6 +8397,8 @@ void Player_Action_Idle(Player* this, PlayState* play) { return; } + GameInteractor_ExecuteOnESS(); + Player_GetMovementSpeedAndYaw(this, &speedTarget, &yawTarget, SPEED_MODE_CURVED, play); if (speedTarget != 0.0f) { @@ -8567,6 +8593,16 @@ void Player_Action_808414F8(Player* this, PlayState* play) { } Player_GetMovementSpeedAndYaw(this, &speedTarget, &yawTarget, SPEED_MODE_LINEAR, play); + + int32_t giSpeedModifier = GameInteractor_MovementSpeedMultiplier(); + if (giSpeedModifier != 0) { + if (giSpeedModifier > 0) { + speedTarget *= giSpeedModifier; + } else { + speedTarget /= abs(giSpeedModifier); + } + } + sp2C = func_8083FD78(this, &speedTarget, &yawTarget, play); if (sp2C >= 0) { @@ -8868,9 +8904,12 @@ void Player_Action_80842180(Player* this, PlayState* play) { if (!func_8083C484(this, &sp2C, &sp2A)) { - if (CVarGetInteger(CVAR_ENHANCEMENT("MMBunnyHood"), BUNNY_HOOD_VANILLA) != BUNNY_HOOD_VANILLA && - this->currentMask == PLAYER_MASK_BUNNY) { - sp2C *= 1.5f; + if (this->currentMask == PLAYER_MASK_BUNNY) { + if (IS_ROGUELIKE) { + sp2C *= 1.0f + (((f32)gSaveContext.ship.quest.data.rogueLike.stats[RL_SPEED]) / 10.0f) * 1.5f; + } else if (CVarGetInteger(CVAR_ENHANCEMENT("MMBunnyHood"), BUNNY_HOOD_VANILLA) != BUNNY_HOOD_VANILLA) { + sp2C *= 1.5f; + } } if (CVarGetInteger(CVAR_SETTING("WalkModifier.Enabled"), 0)) { @@ -8881,9 +8920,12 @@ void Player_Action_80842180(Player* this, PlayState* play) { sp2C *= CVarGetFloat(CVAR_SETTING("WalkModifier.Mapping2"), 1.0f); } } else { - if (CHECK_BTN_ALL(sControlInput->cur.button, BTN_CUSTOM_MODIFIER1)) { + const s32 mod1Mask = CVarGetInteger(CVAR_SETTING("WalkModifier.Mod1Btn"), BTN_CUSTOM_MODIFIER1); + const s32 mod2Mask = CVarGetInteger(CVAR_SETTING("WalkModifier.Mod2Btn"), BTN_CUSTOM_MODIFIER2); + + if (mod1Mask != 0 && CHECK_BTN_ALL(sControlInput->cur.button, mod1Mask)) { sp2C *= CVarGetFloat(CVAR_SETTING("WalkModifier.Mapping1"), 1.0f); - } else if (CHECK_BTN_ALL(sControlInput->cur.button, BTN_CUSTOM_MODIFIER2)) { + } else if (mod2Mask != 0 && CHECK_BTN_ALL(sControlInput->cur.button, mod2Mask)) { sp2C *= CVarGetFloat(CVAR_SETTING("WalkModifier.Mapping2"), 1.0f); } } @@ -10225,6 +10267,8 @@ void Player_Action_WaitForPutAway(Player* this, PlayState* play) { this->stateFlags2 |= PLAYER_STATE2_DISABLE_ROTATION_Z_TARGET | PLAYER_STATE2_DISABLE_ROTATION_ALWAYS; LinkAnimation_Update(play, &this->skelAnime); + GameInteractor_ExecuteOnWaitForPutaway(); + // Wait for the held item put away process to complete. // Determining if the put away process is complete is a bit complicated: // `Player_UpdateUpperBody` will only return false if the current UpperAction returns false. @@ -12400,10 +12444,15 @@ void Player_Update(Actor* thisx, PlayState* play) { if (CVarGetInteger(CVAR_SETTING("WalkModifier.Enabled"), 0) && CVarGetInteger(CVAR_SETTING("WalkModifier.SpeedToggle"), 0)) { - if (CHECK_BTN_ALL(sControlInput->press.button, BTN_CUSTOM_MODIFIER1)) { + const s32 mod1Mask = CVarGetInteger(CVAR_SETTING("WalkModifier.Mod1Btn"), BTN_CUSTOM_MODIFIER1); + const s32 mod2Mask = CVarGetInteger(CVAR_SETTING("WalkModifier.Mod2Btn"), BTN_CUSTOM_MODIFIER2); + + if (mod1Mask != 0 && CHECK_BTN_ALL(sControlInput->cur.button, mod1Mask) && + CHECK_BTN_ANY(sControlInput->press.button, mod1Mask)) { gWalkSpeedToggle1 = !gWalkSpeedToggle1; } - if (CHECK_BTN_ALL(sControlInput->press.button, BTN_CUSTOM_MODIFIER2)) { + if (mod2Mask != 0 && CHECK_BTN_ALL(sControlInput->cur.button, mod2Mask) && + CHECK_BTN_ANY(sControlInput->press.button, mod2Mask)) { gWalkSpeedToggle2 = !gWalkSpeedToggle2; } } @@ -12817,9 +12866,13 @@ s16 func_8084ABD8(PlayState* play, Player* this, s32 arg2, s16 arg3) { if (CVarGetInteger(CVAR_SETTING("MoveInFirstPerson"), 0)) { f32 movementSpeed = LINK_IS_ADULT ? 9.0f : 8.25f; - if (CVarGetInteger(CVAR_ENHANCEMENT("MMBunnyHood"), BUNNY_HOOD_VANILLA) != BUNNY_HOOD_VANILLA && - this->currentMask == PLAYER_MASK_BUNNY) { - movementSpeed *= 1.5f; + + if (this->currentMask == PLAYER_MASK_BUNNY) { + if (IS_ROGUELIKE) { + movementSpeed *= 1.0f + (((f32)gSaveContext.ship.quest.data.rogueLike.stats[RL_SPEED]) / 10.0f) * 1.5f; + } else if (CVarGetInteger(CVAR_ENHANCEMENT("MMBunnyHood"), BUNNY_HOOD_VANILLA) != BUNNY_HOOD_VANILLA) { + movementSpeed *= 1.5f; + } } f32 relX = (sControlInput->rel.stick_x / 10 * -invertXAxisMulti); @@ -12865,9 +12918,12 @@ void func_8084AEEC(Player* this, f32* arg1, f32 arg2, s16 arg3) { // sControlInput is NULL to prevent inputs while surfacing after obtaining an underwater item so we want to // ignore it for that case } else if (sControlInput != NULL) { - if (CHECK_BTN_ALL(sControlInput->cur.button, BTN_CUSTOM_MODIFIER1)) { + const s32 mod1Mask = CVarGetInteger(CVAR_SETTING("WalkModifier.Mod1Btn"), BTN_CUSTOM_MODIFIER1); + const s32 mod2Mask = CVarGetInteger(CVAR_SETTING("WalkModifier.Mod2Btn"), BTN_CUSTOM_MODIFIER2); + + if (mod1Mask != 0 && CHECK_BTN_ALL(sControlInput->cur.button, mod1Mask)) { swimMod *= CVarGetFloat(CVAR_SETTING("WalkModifier.SwimMapping1"), 1.0f); - } else if (CHECK_BTN_ALL(sControlInput->cur.button, BTN_CUSTOM_MODIFIER2)) { + } else if (mod2Mask != 0 && CHECK_BTN_ALL(sControlInput->cur.button, mod2Mask)) { swimMod *= CVarGetFloat(CVAR_SETTING("WalkModifier.SwimMapping2"), 1.0f); } } @@ -15093,11 +15149,10 @@ void Player_Action_8084FBF4(Player* this, PlayState* play) { */ s32 Player_UpdateNoclip(Player* this, PlayState* play) { sControlInput = &play->state.input[0]; + s32 mask = CVarGetInteger("gDeveloperTools.NoClipBtn", BTN_L | BTN_DRIGHT); if (CVarGetInteger(CVAR_DEVELOPER_TOOLS("DebugEnabled"), 0) && - ((CHECK_BTN_ALL(sControlInput->cur.button, BTN_A | BTN_L | BTN_R) && - CHECK_BTN_ALL(sControlInput->press.button, BTN_B)) || - (CHECK_BTN_ALL(sControlInput->cur.button, BTN_L) && CHECK_BTN_ALL(sControlInput->press.button, BTN_DRIGHT)))) { + (CHECK_BTN_ALL(sControlInput->cur.button, mask) && CHECK_BTN_ANY(sControlInput->press.button, mask))) { sNoclipEnabled ^= 1; diff --git a/soh/src/overlays/effects/ovl_Effect_Ss_Bomb2/z_eff_ss_bomb2.c b/soh/src/overlays/effects/ovl_Effect_Ss_Bomb2/z_eff_ss_bomb2.c index 12d5c38cc93..2222d8f9fe0 100644 --- a/soh/src/overlays/effects/ovl_Effect_Ss_Bomb2/z_eff_ss_bomb2.c +++ b/soh/src/overlays/effects/ovl_Effect_Ss_Bomb2/z_eff_ss_bomb2.c @@ -174,23 +174,47 @@ void EffectSsBomb2_Update(PlayState* play, u32 index, EffectSs* this) { this->rDepth += 2.0f; } - if ((this->life < 23) && (this->life > 13)) { - divisor = this->life - 13; - this->rPrimColorR = func_80027DD4(this->rPrimColorR, 255, divisor); - this->rPrimColorG = func_80027DD4(this->rPrimColorG, 255, divisor); - this->rPrimColorB = func_80027DD4(this->rPrimColorB, 150, divisor); - this->rPrimColorA = func_80027DD4(this->rPrimColorA, 255, divisor); - this->rEnvColorR = func_80027DD4(this->rEnvColorR, 150, divisor); - this->rEnvColorG = func_80027DD4(this->rEnvColorG, 0, divisor); - this->rEnvColorB = func_80027DD4(this->rEnvColorB, 0, divisor); - } else if ((this->life < 14) && (this->life > -1)) { - divisor = this->life + 1; - this->rPrimColorR = func_80027DD4(this->rPrimColorR, 50, divisor); - this->rPrimColorG = func_80027DD4(this->rPrimColorG, 50, divisor); - this->rPrimColorB = func_80027DD4(this->rPrimColorB, 50, divisor); - this->rPrimColorA = func_80027DD4(this->rPrimColorA, 150, divisor); - this->rEnvColorR = func_80027DD4(this->rEnvColorR, 10, divisor); - this->rEnvColorG = func_80027DD4(this->rEnvColorG, 10, divisor); - this->rEnvColorB = func_80027DD4(this->rEnvColorB, 10, divisor); + if (CVarGetInteger("gHoliday.Visual.SnowingWeather", 0)) { + // Snowy / white bomb effect + if ((this->life < 23) && (this->life > 13)) { + divisor = this->life - 13; + this->rPrimColorR = func_80027DD4(this->rPrimColorR, 255, divisor); + this->rPrimColorG = func_80027DD4(this->rPrimColorG, 255, divisor); + this->rPrimColorB = func_80027DD4(this->rPrimColorB, 255, divisor); + this->rPrimColorA = func_80027DD4(this->rPrimColorA, 255, divisor); + this->rEnvColorR = func_80027DD4(this->rEnvColorR, 255, divisor); + this->rEnvColorG = func_80027DD4(this->rEnvColorG, 255, divisor); + this->rEnvColorB = func_80027DD4(this->rEnvColorB, 255, divisor); + } else if ((this->life < 14) && (this->life > -1)) { + divisor = this->life + 1; + this->rPrimColorR = func_80027DD4(this->rPrimColorR, 255, divisor); + this->rPrimColorG = func_80027DD4(this->rPrimColorG, 255, divisor); + this->rPrimColorB = func_80027DD4(this->rPrimColorB, 255, divisor); + this->rPrimColorA = func_80027DD4(this->rPrimColorA, 255, divisor); + this->rEnvColorR = func_80027DD4(this->rEnvColorR, 255, divisor); + this->rEnvColorG = func_80027DD4(this->rEnvColorG, 255, divisor); + this->rEnvColorB = func_80027DD4(this->rEnvColorB, 255, divisor); + } + } else { + // Vanilla bomb colors + if ((this->life < 23) && (this->life > 13)) { + divisor = this->life - 13; + this->rPrimColorR = func_80027DD4(this->rPrimColorR, 255, divisor); + this->rPrimColorG = func_80027DD4(this->rPrimColorG, 255, divisor); + this->rPrimColorB = func_80027DD4(this->rPrimColorB, 150, divisor); + this->rPrimColorA = func_80027DD4(this->rPrimColorA, 255, divisor); + this->rEnvColorR = func_80027DD4(this->rEnvColorR, 150, divisor); + this->rEnvColorG = func_80027DD4(this->rEnvColorG, 0, divisor); + this->rEnvColorB = func_80027DD4(this->rEnvColorB, 0, divisor); + } else if ((this->life < 14) && (this->life > -1)) { + divisor = this->life + 1; + this->rPrimColorR = func_80027DD4(this->rPrimColorR, 50, divisor); + this->rPrimColorG = func_80027DD4(this->rPrimColorG, 50, divisor); + this->rPrimColorB = func_80027DD4(this->rPrimColorB, 50, divisor); + this->rPrimColorA = func_80027DD4(this->rPrimColorA, 150, divisor); + this->rEnvColorR = func_80027DD4(this->rEnvColorR, 10, divisor); + this->rEnvColorG = func_80027DD4(this->rEnvColorG, 10, divisor); + this->rEnvColorB = func_80027DD4(this->rEnvColorB, 10, divisor); + } } } diff --git a/soh/src/overlays/gamestates/ovl_file_choose/file_choose.h b/soh/src/overlays/gamestates/ovl_file_choose/file_choose.h index 5f6e3f202e5..d97d17399a2 100644 --- a/soh/src/overlays/gamestates/ovl_file_choose/file_choose.h +++ b/soh/src/overlays/gamestates/ovl_file_choose/file_choose.h @@ -70,6 +70,11 @@ typedef enum { CM_START_RANDOMIZER_SETTINGS_MENU, CM_RANDOMIZER_SETTINGS_MENU_TO_QUEST, CM_NAME_ENTRY_TO_RANDOMIZER_SETTINGS_MENU, + CM_ROTATE_TO_ARCHIPELAGO_MENU, + CM_ARCHIPELAGO_SETTINGS_MENU, + CM_START_ARCHIPELAGO_SETTINGS_MENU, + CM_ARCHIPELAGO_SETTINGS_TO_QUEST, + CM_NAME_ENTRY_TO_ARCHIPELAGO_SETTINGS_MENU, } ConfigMode; typedef enum { diff --git a/soh/src/overlays/gamestates/ovl_file_choose/z_file_choose.c b/soh/src/overlays/gamestates/ovl_file_choose/z_file_choose.c index 1870e760d13..d28e03b267b 100644 --- a/soh/src/overlays/gamestates/ovl_file_choose/z_file_choose.c +++ b/soh/src/overlays/gamestates/ovl_file_choose/z_file_choose.c @@ -452,7 +452,7 @@ u8 ShouldRenderItem(s16 fileIndex, u8 item) { // greg if (item == ITEM_RUPEE_GREEN) { - return Save_GetSaveMetaInfo(fileIndex)->randoSave; + return Save_GetSaveMetaInfo(fileIndex)->randoSave || Save_GetSaveMetaInfo(fileIndex)->archiSave; } return 1; @@ -704,7 +704,7 @@ static void DrawMoreInfo(FileChooseContext* this, s16 fileIndex, u8 alpha) { } #define MIN_QUEST (ResourceMgr_GameHasOriginal() ? QUEST_NORMAL : QUEST_MASTER) -#define MAX_QUEST QUEST_BOSSRUSH +#define MAX_QUEST QUEST_ROGUELIKE void Sram_InitDebugSave(void); void Sram_InitBossRushSave(); @@ -1356,6 +1356,11 @@ void FileChoose_UpdateQuestMenu(GameState* thisx) { &gSfxDefaultFreqAndVolScale, &gSfxDefaultReverb); this->prevConfigMode = this->configMode; this->configMode = CM_ROTATE_TO_RANDOMIZER_SETTINGS_MENU; + } else if (this->questType[this->buttonIndex] == QUEST_ARCHIPELAGO) { + Audio_PlaySoundGeneral(NA_SE_SY_FSEL_DECIDE_L, &gSfxDefaultPos, 4, &gSfxDefaultFreqAndVolScale, + &gSfxDefaultFreqAndVolScale, &gSfxDefaultReverb); + this->prevConfigMode = this->configMode; + this->configMode = CM_ROTATE_TO_ARCHIPELAGO_MENU; } else { Audio_PlaySoundGeneral(NA_SE_SY_FSEL_DECIDE_L, &gSfxDefaultPos, 4, &gSfxDefaultFreqAndVolScale, &gSfxDefaultFreqAndVolScale, &gSfxDefaultReverb); @@ -1450,6 +1455,7 @@ void FileChoose_UpdateRandomizerMenu(GameState* thisx) { if (CHECK_BTN_ALL(input->press.button, BTN_A)) { if (this->randomizerIndex == RSM_START_RANDOMIZER) { if (Randomizer_IsSeedGenerated() || Randomizer_IsSpoilerLoaded()) { + SohFileSelect_ShowPresetModal(); Audio_PlaySoundGeneral(NA_SE_SY_FSEL_DECIDE_L, &gSfxDefaultPos, 4, &gSfxDefaultFreqAndVolScale, &gSfxDefaultFreqAndVolScale, &gSfxDefaultReverb); static u8 emptyName[] = { 0x3E, 0x3E, 0x3E, 0x3E, 0x3E, 0x3E, 0x3E, 0x3E }; @@ -1532,7 +1538,7 @@ void FileChoose_RotateToNameEntry(GameState* thisx) { this->windowRot += VREG(16); - if (this->prevConfigMode == CM_RANDOMIZER_SETTINGS_MENU) { + if (this->prevConfigMode == CM_RANDOMIZER_SETTINGS_MENU || this->prevConfigMode == CM_ARCHIPELAGO_SETTINGS_MENU) { if (this->windowRot >= 942.0f) { this->windowRot = 628.0f; this->configMode = CM_START_NAME_ENTRY; @@ -1589,7 +1595,8 @@ void FileChoose_RotateToQuest(GameState* thisx) { FileChooseContext* this = (FileChooseContext*)thisx; if (this->configMode == CM_NAME_ENTRY_TO_QUEST_MENU || this->configMode == CM_BOSS_RUSH_TO_QUEST || - this->configMode == CM_RANDOMIZER_SETTINGS_MENU_TO_QUEST) { + this->configMode == CM_RANDOMIZER_SETTINGS_MENU_TO_QUEST || + this->configMode == CM_ARCHIPELAGO_SETTINGS_TO_QUEST) { this->windowRot -= VREG(16); if (this->windowRot <= 314.0f) { @@ -1637,67 +1644,251 @@ void FileChoose_RotateToRandomizer(GameState* thisx) { } } -static void (*gConfigModeUpdateFuncs[])(GameState*) = { - FileChoose_StartFadeIn, FileChoose_FinishFadeIn, - FileChoose_UpdateMainMenu, FileChoose_SetupCopySource, - FileChoose_SelectCopySource, FileChoose_SetupCopyDest1, - FileChoose_SetupCopyDest2, FileChoose_SelectCopyDest, - FileChoose_ExitToCopySource1, FileChoose_ExitToCopySource2, - FileChoose_SetupCopyConfirm1, FileChoose_SetupCopyConfirm2, - FileChoose_CopyConfirm, FileChoose_ReturnToCopyDest, - FileChoose_CopyAnim1, FileChoose_CopyAnim2, - FileChoose_CopyAnim3, FileChoose_CopyAnim4, - FileChoose_CopyAnim5, FileChoose_ExitCopyToMain, - FileChoose_SetupEraseSelect, FileChoose_EraseSelect, - FileChoose_SetupEraseConfirm1, FileChoose_SetupEraseConfirm2, - FileChoose_EraseConfirm, FileChoose_ExitToEraseSelect1, - FileChoose_ExitToEraseSelect2, FileChoose_EraseAnim1, - FileChoose_EraseAnim2, FileChoose_EraseAnim3, - FileChoose_ExitEraseToMain, FileChoose_UnusedCM31, - FileChoose_RotateToNameEntry, FileChoose_UpdateKeyboardCursor, - FileChoose_StartNameEntry, FileChoose_RotateToMain, - FileChoose_RotateToOptions, FileChoose_UpdateOptionsMenu, - FileChoose_StartOptions, FileChoose_RotateToMain, - FileChoose_UnusedCMDelay, FileChoose_RotateToQuest, - FileChoose_UpdateQuestMenu, FileChoose_StartQuestMenu, - FileChoose_RotateToMain, FileChoose_RotateToQuest, - FileChoose_RotateToBossRush, FileChoose_UpdateBossRushMenu, - FileChoose_StartBossRushMenu, FileChoose_RotateToQuest, - FileChoose_RotateToRandomizer, FileChoose_UpdateRandomizerMenu, - FileChoose_StartRandomizerMenu, FileChoose_RotateToQuest, - FileChoose_RotateToRandomizer, -}; +void FileChoose_RotateToArchipelago(GameState* thisx) { + FileChooseContext* this = (FileChooseContext*)thisx; -static void (*gConfigModeUpdateFuncsNES[])(GameState*) = { - FileChoose_StartFadeIn, FileChoose_FinishFadeIn, - FileChoose_UpdateMainMenu, FileChoose_SetupCopySource, - FileChoose_SelectCopySource, FileChoose_SetupCopyDest1, - FileChoose_SetupCopyDest2, FileChoose_SelectCopyDest, - FileChoose_ExitToCopySource1, FileChoose_ExitToCopySource2, - FileChoose_SetupCopyConfirm1, FileChoose_SetupCopyConfirm2, - FileChoose_CopyConfirm, FileChoose_ReturnToCopyDest, - FileChoose_CopyAnim1, FileChoose_CopyAnim2, - FileChoose_CopyAnim3, FileChoose_CopyAnim4, - FileChoose_CopyAnim5, FileChoose_ExitCopyToMain, - FileChoose_SetupEraseSelect, FileChoose_EraseSelect, - FileChoose_SetupEraseConfirm1, FileChoose_SetupEraseConfirm2, - FileChoose_EraseConfirm, FileChoose_ExitToEraseSelect1, - FileChoose_ExitToEraseSelect2, FileChoose_EraseAnim1, - FileChoose_EraseAnim2, FileChoose_EraseAnim3, - FileChoose_ExitEraseToMain, FileChoose_UnusedCM31, - FileChoose_RotateToNameEntry, FileChoose_UpdateKeyboardCursorNES, - FileChoose_StartNameEntryNES, FileChoose_RotateToMain, - FileChoose_RotateToOptions, FileChoose_UpdateOptionsMenuNES, - FileChoose_StartOptionsNES, FileChoose_RotateToMain, - FileChoose_UnusedCMDelay, FileChoose_RotateToQuest, - FileChoose_UpdateQuestMenu, FileChoose_StartQuestMenu, - FileChoose_RotateToMain, FileChoose_RotateToQuest, - FileChoose_RotateToBossRush, FileChoose_UpdateBossRushMenu, - FileChoose_StartBossRushMenu, FileChoose_RotateToQuest, - FileChoose_RotateToRandomizer, FileChoose_UpdateRandomizerMenu, - FileChoose_StartRandomizerMenu, FileChoose_RotateToQuest, - FileChoose_RotateToRandomizer, -}; + if (this->configMode == CM_NAME_ENTRY_TO_ARCHIPELAGO_SETTINGS_MENU) { + this->windowRot -= VREG(16); + + if (this->windowRot <= 314.0f) { + this->windowRot = 628.0f; + this->configMode = CM_START_ARCHIPELAGO_SETTINGS_MENU; + } + } else { + this->windowRot += VREG(16); + + if (this->windowRot >= 628.0f) { + this->windowRot = 628.0f; + this->configMode = CM_START_ARCHIPELAGO_SETTINGS_MENU; + } + } +} + +void FileChoose_UpdateArchipelagoMenu(GameState* thisx) { + FileChooseContext* this = (FileChooseContext*)thisx; + Input* input = &this->state.input[0]; + bool dpad = CVarGetInteger(CVAR_SETTING("DpadInText"), 0); + + // Fade in elements after opening Archipelago Options menu + this->archipelagoUIAlpha += 25; + if (this->archipelagoUIAlpha > 255) { + this->archipelagoUIAlpha = 255; + } + + // Move menu selection up or down + if (ABS(this->stickRelY) > 30 || (dpad && CHECK_BTN_ANY(input->press.button, BTN_DDOWN | BTN_DUP))) { + // move down + if (this->stickRelY < -30 || (dpad && CHECK_BTN_ANY(input->press.button, BTN_DDOWN))) { + if ((this->archipelagoIndex + 1) > ASM_CHANGE_CONNECTION_INFO) { + this->archipelagoIndex = ASM_START_ARCHIPELAGO; + } else { + this->archipelagoIndex++; + } + } else { + if (((int8_t)this->archipelagoIndex - 1) < ASM_START_ARCHIPELAGO) { + this->archipelagoIndex = ASM_CHANGE_CONNECTION_INFO; + } else { + this->archipelagoIndex--; + } + } + Audio_PlaySoundGeneral(NA_SE_SY_FSEL_CURSOR, &gSfxDefaultPos, 4, &gSfxDefaultFreqAndVolScale, + &gSfxDefaultFreqAndVolScale, &gSfxDefaultReverb); + } + + if (CHECK_BTN_ALL(input->press.button, BTN_B)) { + this->configMode = CM_ARCHIPELAGO_SETTINGS_TO_QUEST; + return; + } + + if (CHECK_BTN_ALL(input->press.button, BTN_A)) { + if (this->archipelagoIndex == ASM_START_ARCHIPELAGO) { + // Only continue when connected to a slot and locations are scouted. + if (CVarGetInteger(CVAR_REMOTE_ARCHIPELAGO("ConnectionStatus"), 0) != 4) { + Audio_PlaySoundGeneral(NA_SE_SY_FSEL_ERROR, &gSfxDefaultPos, 4, &gSfxDefaultFreqAndVolScale, + &gSfxDefaultFreqAndVolScale, &gSfxDefaultReverb); + return; + } + SohFileSelect_ShowPresetModal(); + Audio_PlaySoundGeneral(NA_SE_SY_FSEL_DECIDE_L, &gSfxDefaultPos, 4, &gSfxDefaultFreqAndVolScale, + &gSfxDefaultFreqAndVolScale, &gSfxDefaultReverb); + ParseArchipelago(); + static u8 emptyName[] = { 0x3E, 0x3E, 0x3E, 0x3E, 0x3E, 0x3E, 0x3E, 0x3E }; + static u8 emptyNameNES[] = { 0xDF, 0xDF, 0xDF, 0xDF, 0xDF, 0xDF, 0xDF, 0xDF }; + static u8 linkName[] = { 0x15, 0x2C, 0x31, 0x2E, 0x3E, 0x3E, 0x3E, 0x3E }; + static u8 linkNameNES[] = { 0xB6, 0xB3, 0xB8, 0xB5, 0xDF, 0xDF, 0xDF, 0xDF }; + static u8 linkNameJP[] = { 0x81, 0x87, 0x61, 0xDF, 0xDF, 0xDF, 0xDF, 0xDF }; + u8* defaultName; + + this->prevConfigMode = this->configMode; + this->configMode = CM_ROTATE_TO_NAME_ENTRY; + this->logoAlpha = 0; + CVarSetInteger(CVAR_GENERAL("OnFileSelectNameEntry"), 1); + this->kbdButton = FS_KBD_BTN_NONE; + this->charPage = FS_CHAR_PAGE_ENG; + this->kbdX = 0; + this->kbdY = 0; + this->charIndex = 0; + this->charBgAlpha = 0; + this->newFileNameCharCount = CVarGetInteger(CVAR_ENHANCEMENT("LinkDefaultName"), 0) ? 4 : 0; + this->nameEntryBoxPosX = 120; + this->nameEntryBoxAlpha = 0; + if (ResourceMgr_GetGameRegion(0) == GAME_REGION_PAL && gSaveContext.language != LANGUAGE_JPN) { + defaultName = CVarGetInteger(CVAR_ENHANCEMENT("LinkDefaultName"), 0) ? &linkName : &emptyName; + } else if (gSaveContext.language == LANGUAGE_JPN) { // Japanese + if (CVarGetInteger(CVAR_ENHANCEMENT("LinkDefaultName"), 0) != 0) { + // Set player name to "リンク" ("Link" in Katakana, 3 characters long) when playing in Japanese. + defaultName = &linkNameJP; + this->newFileNameCharCount = 3; + } else { + defaultName = &emptyNameNES; + } + this->charPage = FS_CHAR_PAGE_HIRA; // Default to Hiragana Keyboard + } else { // GAME_REGION_NTSC + defaultName = CVarGetInteger(CVAR_ENHANCEMENT("LinkDefaultName"), 0) ? &linkNameNES : &emptyNameNES; + } + memcpy(Save_GetSaveMetaInfo(this->buttonIndex)->playerName, defaultName, 8); + } else if (this->archipelagoIndex == ASM_CHANGE_CONNECTION_INFO) { + Audio_PlaySoundGeneral(NA_SE_SY_FSEL_DECIDE_L, &gSfxDefaultPos, 4, &gSfxDefaultFreqAndVolScale, + &gSfxDefaultFreqAndVolScale, &gSfxDefaultReverb); + Archipelago_ShowArchipelagoMenu(); + } + } +} + +void FileChoose_StartArchipelagoMenu(GameState* thisx) { + FileChooseContext* this = (FileChooseContext*)thisx; + + this->logoAlpha -= 25; + this->archipelagoUIAlpha = 0; + this->archipelagoArrowOffset = 0; + + if (this->logoAlpha <= 0) { + this->logoAlpha = 0; + this->configMode = CM_ARCHIPELAGO_SETTINGS_MENU; + } +} + +static void (*gConfigModeUpdateFuncs[])(GameState*) = { FileChoose_StartFadeIn, + FileChoose_FinishFadeIn, + FileChoose_UpdateMainMenu, + FileChoose_SetupCopySource, + FileChoose_SelectCopySource, + FileChoose_SetupCopyDest1, + FileChoose_SetupCopyDest2, + FileChoose_SelectCopyDest, + FileChoose_ExitToCopySource1, + FileChoose_ExitToCopySource2, + FileChoose_SetupCopyConfirm1, + FileChoose_SetupCopyConfirm2, + FileChoose_CopyConfirm, + FileChoose_ReturnToCopyDest, + FileChoose_CopyAnim1, + FileChoose_CopyAnim2, + FileChoose_CopyAnim3, + FileChoose_CopyAnim4, + FileChoose_CopyAnim5, + FileChoose_ExitCopyToMain, + FileChoose_SetupEraseSelect, + FileChoose_EraseSelect, + FileChoose_SetupEraseConfirm1, + FileChoose_SetupEraseConfirm2, + FileChoose_EraseConfirm, + FileChoose_ExitToEraseSelect1, + FileChoose_ExitToEraseSelect2, + FileChoose_EraseAnim1, + FileChoose_EraseAnim2, + FileChoose_EraseAnim3, + FileChoose_ExitEraseToMain, + FileChoose_UnusedCM31, + FileChoose_RotateToNameEntry, + FileChoose_UpdateKeyboardCursor, + FileChoose_StartNameEntry, + FileChoose_RotateToMain, + FileChoose_RotateToOptions, + FileChoose_UpdateOptionsMenu, + FileChoose_StartOptions, + FileChoose_RotateToMain, + FileChoose_UnusedCMDelay, + FileChoose_RotateToQuest, + FileChoose_UpdateQuestMenu, + FileChoose_StartQuestMenu, + FileChoose_RotateToMain, + FileChoose_RotateToQuest, + FileChoose_RotateToBossRush, + FileChoose_UpdateBossRushMenu, + FileChoose_StartBossRushMenu, + FileChoose_RotateToQuest, + FileChoose_RotateToRandomizer, + FileChoose_UpdateRandomizerMenu, + FileChoose_StartRandomizerMenu, + FileChoose_RotateToQuest, + FileChoose_RotateToRandomizer, + FileChoose_RotateToArchipelago, + FileChoose_UpdateArchipelagoMenu, + FileChoose_StartArchipelagoMenu, + FileChoose_RotateToQuest, + FileChoose_RotateToArchipelago }; + +static void (*gConfigModeUpdateFuncsNES[])(GameState*) = { FileChoose_StartFadeIn, + FileChoose_FinishFadeIn, + FileChoose_UpdateMainMenu, + FileChoose_SetupCopySource, + FileChoose_SelectCopySource, + FileChoose_SetupCopyDest1, + FileChoose_SetupCopyDest2, + FileChoose_SelectCopyDest, + FileChoose_ExitToCopySource1, + FileChoose_ExitToCopySource2, + FileChoose_SetupCopyConfirm1, + FileChoose_SetupCopyConfirm2, + FileChoose_CopyConfirm, + FileChoose_ReturnToCopyDest, + FileChoose_CopyAnim1, + FileChoose_CopyAnim2, + FileChoose_CopyAnim3, + FileChoose_CopyAnim4, + FileChoose_CopyAnim5, + FileChoose_ExitCopyToMain, + FileChoose_SetupEraseSelect, + FileChoose_EraseSelect, + FileChoose_SetupEraseConfirm1, + FileChoose_SetupEraseConfirm2, + FileChoose_EraseConfirm, + FileChoose_ExitToEraseSelect1, + FileChoose_ExitToEraseSelect2, + FileChoose_EraseAnim1, + FileChoose_EraseAnim2, + FileChoose_EraseAnim3, + FileChoose_ExitEraseToMain, + FileChoose_UnusedCM31, + FileChoose_RotateToNameEntry, + FileChoose_UpdateKeyboardCursorNES, + FileChoose_StartNameEntryNES, + FileChoose_RotateToMain, + FileChoose_RotateToOptions, + FileChoose_UpdateOptionsMenuNES, + FileChoose_StartOptionsNES, + FileChoose_RotateToMain, + FileChoose_UnusedCMDelay, + FileChoose_RotateToQuest, + FileChoose_UpdateQuestMenu, + FileChoose_StartQuestMenu, + FileChoose_RotateToMain, + FileChoose_RotateToQuest, + FileChoose_RotateToBossRush, + FileChoose_UpdateBossRushMenu, + FileChoose_StartBossRushMenu, + FileChoose_RotateToQuest, + FileChoose_RotateToRandomizer, + FileChoose_UpdateRandomizerMenu, + FileChoose_StartRandomizerMenu, + FileChoose_RotateToQuest, + FileChoose_RotateToRandomizer, + FileChoose_RotateToArchipelago, + FileChoose_UpdateArchipelagoMenu, + FileChoose_StartArchipelagoMenu, + FileChoose_RotateToQuest, + FileChoose_RotateToArchipelago }; /** * Updates the alpha of the cursor to make it pulsate. @@ -2185,7 +2376,8 @@ void FileChoose_DrawFileInfo(GameState* thisx, s16 fileIndex, s16 isActive) { &deathCountSplit[2]); // draw death count - if (CVarGetInteger(CVAR_ENHANCEMENT("FileSelectMoreInfo"), 0) == 0 || this->menuMode != FS_MENU_MODE_SELECT) { + if (CVarGetInteger(CVAR_ENHANCEMENT("FileSelectMoreInfo"), 0) == 0 || this->menuMode != FS_MENU_MODE_SELECT || + Save_GetSaveMetaInfo(this->selectedFileIndex)->archiSave) { for (i = 0, vtxOffset = 0; i < 3; i++, vtxOffset += 4) { FileChoose_DrawCharacter(this->state.gfxCtx, sp54->fontBuf + deathCountSplit[i] * FONT_CHAR_TEX_SIZE, vtxOffset); @@ -2211,7 +2403,8 @@ void FileChoose_DrawFileInfo(GameState* thisx, s16 fileIndex, s16 isActive) { i = Save_GetSaveMetaInfo(fileIndex)->healthCapacity / FULL_HEART_HEALTH; - if (CVarGetInteger(CVAR_ENHANCEMENT("FileSelectMoreInfo"), 0) == 0 || this->menuMode != FS_MENU_MODE_SELECT) { + if (CVarGetInteger(CVAR_ENHANCEMENT("FileSelectMoreInfo"), 0) == 0 || this->menuMode != FS_MENU_MODE_SELECT || + Save_GetSaveMetaInfo(this->selectedFileIndex)->archiSave) { // draw hearts for (vtxOffset = 0, j = 0; j < i; j++, vtxOffset += 4) { gSPVertex(POLY_OPA_DISP++, &this->windowContentVtx[D_8081284C[fileIndex] + vtxOffset] + 0x30, 4, 0); @@ -2228,7 +2421,8 @@ void FileChoose_DrawFileInfo(GameState* thisx, s16 fileIndex, s16 isActive) { textAlpha = 255; } - if (CVarGetInteger(CVAR_ENHANCEMENT("FileSelectMoreInfo"), 0) != 0 && this->menuMode == FS_MENU_MODE_SELECT) { + if (CVarGetInteger(CVAR_ENHANCEMENT("FileSelectMoreInfo"), 0) != 0 && this->menuMode == FS_MENU_MODE_SELECT && + Save_GetSaveMetaInfo(this->selectedFileIndex)->archiSave == 0) { DrawMoreInfo(this, fileIndex, textAlpha); } else { // draw quest items @@ -2252,6 +2446,57 @@ void FileChoose_DrawFileInfo(GameState* thisx, s16 fileIndex, s16 isActive) { } } } + + if (Save_GetSaveMetaInfo(this->selectedFileIndex)->archiSave) { + uint8_t language = (gSaveContext.language == LANGUAGE_JPN) ? LANGUAGE_ENG : gSaveContext.language; + + // Connection status text + int statusPos = 61 + Interface_DrawTextLine(this->state.gfxCtx, + SohFileSelect_GetArchipelagoSettingText(ASM_STATUS, language), + 58, 133, 200, 200, 200, textAlpha, 0.8f, true); + + const bool connectedToThisSlot = + checkArchipelagoSlotInfo(Save_GetSaveMetaInfo(this->selectedFileIndex)->slotName, + Save_GetSaveMetaInfo(this->selectedFileIndex)->archiRoomSeed); + + switch (CVarGetInteger(CVAR_REMOTE_ARCHIPELAGO("ConnectionStatus"), 0)) { + case 0: // Not Connected + Interface_DrawTextLine(this->state.gfxCtx, + SohFileSelect_GetArchipelagoSettingText(ASM_NOT_CONNECTED, language), + statusPos, 133, 255, 120, 120, textAlpha, 0.8f, true); + break; + case 1: // Connecting + case 2: // Connection error, retrying + case 3: // Connected + Interface_DrawTextLine(this->state.gfxCtx, + SohFileSelect_GetArchipelagoSettingText(ASM_CONNECTING, language), statusPos, + 133, 185, 185, 185, textAlpha, 0.8f, true); + break; + case 4: // Connected + Locations Scouted + if (connectedToThisSlot) { + Interface_DrawTextLine(this->state.gfxCtx, + SohFileSelect_GetArchipelagoSettingText(ASM_CONNECTED, language), + statusPos, 133, 120, 255, 120, textAlpha, 0.8f, true); + } else { + Interface_DrawTextLine( + this->state.gfxCtx, + SohFileSelect_GetArchipelagoSettingText(ASM_CHAR_SELECT_CONNECTED_TO_OTHER_SLOT, language), + statusPos, 133, 255, 255, 120, textAlpha, 0.8f, true); + } + + break; + } + + if (!connectedToThisSlot) { + Interface_DrawTextLine(this->state.gfxCtx, + SohFileSelect_GetArchipelagoSettingText(ASM_CHAR_START_TO_CONNECT, language), 58, + 144, 200, 200, 200, textAlpha, 0.8f, true); + } + + // Interface_DrawTextLine(this->state.gfxCtx, + // SohFileSelect_GetArchipelagoSettingText(ASM_CHAR_SELECT_CHANGE_CONNECTION_INFO, language), 95, 220, + // 100, 250, 255, textAlpha, 1.0f, true); + } } CLOSE_DISPS(this->state.gfxCtx); @@ -2359,6 +2604,7 @@ void FileChoose_DrawWindowContents(GameState* thisx) { case CM_NAME_ENTRY_TO_QUEST_MENU: case CM_ROTATE_TO_BOSS_RUSH_MENU: case CM_ROTATE_TO_RANDOMIZER_SETTINGS_MENU: + case CM_ROTATE_TO_ARCHIPELAGO_MENU: tex = FileChoose_GetQuestChooseTitleTexName(gSaveContext.language); break; case CM_BOSS_RUSH_MENU: @@ -2368,6 +2614,10 @@ void FileChoose_DrawWindowContents(GameState* thisx) { case CM_START_RANDOMIZER_SETTINGS_MENU: case CM_RANDOMIZER_SETTINGS_MENU_TO_QUEST: case CM_NAME_ENTRY_TO_RANDOMIZER_SETTINGS_MENU: + case CM_START_ARCHIPELAGO_SETTINGS_MENU: + case CM_ARCHIPELAGO_SETTINGS_MENU: + case CM_ARCHIPELAGO_SETTINGS_TO_QUEST: + case CM_NAME_ENTRY_TO_ARCHIPELAGO_SETTINGS_MENU: tex = FileChoose_GetSohOptionsTitleTexName(gSaveContext.language); break; default: @@ -2484,6 +2734,28 @@ void FileChoose_DrawWindowContents(GameState* thisx) { ResourceMgr_GameHasOriginal() ? gTitleZeldaShieldLogoTex : gTitleZeldaShieldLogoMQTex, 160, 160); FileChoose_DrawImageRGBA32(this->state.gfxCtx, 182, 180, gTitleBossRushSubtitleTex, 128, 32); break; + case QUEST_ARCHIPELAGO: + gDPSetPrimColor(POLY_OPA_DISP++, 0, 0, 255, 255, 255, this->logoAlpha); + FileChoose_DrawTextureI8(this->state.gfxCtx, gTitleTheLegendOfTextTex, 72, 8, 156, 108, 72, 8, 1024, + 1024); + FileChoose_DrawTextureI8(this->state.gfxCtx, gTitleOcarinaOfTimeTMTextTex, 96, 8, 154, 163, 96, 8, 1024, + 1024); + FileChoose_DrawImageRGBA32( + this->state.gfxCtx, 160, 135, + ResourceMgr_GameHasOriginal() ? gTitleZeldaShieldLogoTex : gTitleZeldaShieldLogoMQTex, 160, 160); + FileChoose_DrawImageRGBA32(this->state.gfxCtx, 182, 180, gTitleArchipelagoSubtitleTex, 128, 32); + break; + case QUEST_ROGUELIKE: + gDPSetPrimColor(POLY_OPA_DISP++, 0, 0, 255, 255, 255, this->logoAlpha); + FileChoose_DrawTextureI8(this->state.gfxCtx, gTitleTheLegendOfTextTex, 72, 8, 156, 108, 72, 8, 1024, + 1024); + FileChoose_DrawTextureI8(this->state.gfxCtx, gTitleOcarinaOfTimeTMTextTex, 96, 8, 154, 163, 96, 8, 1024, + 1024); + FileChoose_DrawImageRGBA32( + this->state.gfxCtx, 160, 135, + ResourceMgr_GameHasOriginal() ? gTitleZeldaShieldLogoTex : gTitleZeldaShieldLogoMQTex, 160, 160); + FileChoose_DrawImageRGBA32(this->state.gfxCtx, 182, 180, gTitleRogueLikeSubtitleTex, 128, 32); + break; } } else if (this->configMode == CM_BOSS_RUSH_MENU) { FileChoose_DrawBossRushMenuWindowContents(this); @@ -2508,14 +2780,14 @@ void FileChoose_DrawWindowContents(GameState* thisx) { textColorR = textColorG = textColorB = 100; } - Interface_DrawTextLine(this->state.gfxCtx, SohFileSelect_GetSettingText(index, language), 70, + Interface_DrawTextLine(this->state.gfxCtx, SohFileSelect_GetRandomizerSettingText(index, language), 70, (80 + (index * 16)), textColorR, textColorG, textColorB, textAlpha, 0.8f, true); } // Show text to indicate randomizer is being generated. if (generating) { - Interface_DrawTextLine(this->state.gfxCtx, SohFileSelect_GetSettingText(RSM_GENERATING, language), 70, - (80 + 64), 255, 255, 255, textAlpha, 0.8f, true); + Interface_DrawTextLine(this->state.gfxCtx, SohFileSelect_GetRandomizerSettingText(RSM_GENERATING, language), + 70, (80 + 64), 255, 255, 255, textAlpha, 0.8f, true); } // If no randomizer is generated and "start randomizer" is selected, show text to explain why user can't start @@ -2523,8 +2795,8 @@ void FileChoose_DrawWindowContents(GameState* thisx) { if (!Randomizer_IsSeedGenerated() && !Randomizer_IsSpoilerLoaded() && this->randomizerIndex == RSM_START_RANDOMIZER) { Interface_DrawTextLine(this->state.gfxCtx, - SohFileSelect_GetSettingText(RSM_NO_RANDOMIZER_GENERATED, language), 70, (80 + 64), - 240, 80, 80, textAlpha, 0.8f, true); + SohFileSelect_GetRandomizerSettingText(RSM_NO_RANDOMIZER_GENERATED, language), 70, + (80 + 64), 240, 80, 80, textAlpha, 0.8f, true); } uint16_t textOffset = 16 * this->randomizerIndex; @@ -2537,12 +2809,91 @@ void FileChoose_DrawWindowContents(GameState* thisx) { this->stickRightPrompt.arrowColorG, this->stickRightPrompt.arrowColorB, textAlpha, 62, (85 + textOffset), 0.42f, 0, 0, 1.0f, 1.0f); + } else if (this->configMode == CM_ARCHIPELAGO_SETTINGS_MENU) { + uint8_t language = (gSaveContext.language == LANGUAGE_JPN) ? LANGUAGE_ENG : gSaveContext.language; + uint8_t textAlpha = this->archipelagoUIAlpha; + + uint8_t textIndex = 0; + + for (uint8_t index = 0; index <= ASM_CHANGE_CONNECTION_INFO; index++) { + uint8_t textColorR = 255; + uint8_t textColorG = 255; + uint8_t textColorB = 255; + + // If current index is the selected one, make the text yellow. + if (this->archipelagoIndex == index) { + textColorB = 80; + } + + // If not connected, make Start Archipelago text gray. + if (index == ASM_START_ARCHIPELAGO && CVarGetInteger(CVAR_REMOTE_ARCHIPELAGO("ConnectionStatus"), 0) != 4) { + textColorR = textColorG = textColorB = 100; + } + + Interface_DrawTextLine(this->state.gfxCtx, SohFileSelect_GetArchipelagoSettingText(index, language), 70, + (80 + index * 16), textColorR, textColorG, textColorB, textAlpha, 0.8f, true); + } + + // Server address text + Interface_DrawTextLine(this->state.gfxCtx, + SohFileSelect_GetArchipelagoSettingText(ASM_SERVER_ADDRESS, language), 70, 120, 255, 255, + 255, textAlpha, 0.8f, true); + textIndex++; + Interface_DrawTextLine(this->state.gfxCtx, CVarGetString(CVAR_REMOTE_ARCHIPELAGO("ServerAddress"), "No Data"), + 70, 130, 185, 185, 185, textAlpha, 0.8f, true); + + // Slot name text + textIndex++; + Interface_DrawTextLine(this->state.gfxCtx, SohFileSelect_GetArchipelagoSettingText(ASM_SLOT_NAME, language), 70, + 145, 255, 255, 255, textAlpha, 0.8f, true); + textIndex++; + Interface_DrawTextLine(this->state.gfxCtx, CVarGetString(CVAR_REMOTE_ARCHIPELAGO("SlotName"), "No Data"), 70, + 155, 185, 185, 185, textAlpha, 0.8f, true); + + // Connection status text + int statusPos = 75 + Interface_DrawTextLine(this->state.gfxCtx, + SohFileSelect_GetArchipelagoSettingText(ASM_STATUS, language), 70, + 175, 255, 255, 255, textAlpha, 0.8f, true); + + switch (CVarGetInteger(CVAR_REMOTE_ARCHIPELAGO("ConnectionStatus"), 0)) { + case 0: // Not Connected + Interface_DrawTextLine(this->state.gfxCtx, + SohFileSelect_GetArchipelagoSettingText(ASM_NOT_CONNECTED, language), statusPos, + 175, 255, 120, 120, textAlpha, 0.8f, true); + break; + case 1: // Connecting + case 2: // Connection error, retrying + case 3: // Connected + Interface_DrawTextLine(this->state.gfxCtx, + SohFileSelect_GetArchipelagoSettingText(ASM_CONNECTING, language), statusPos, + 175, 185, 185, 185, textAlpha, 0.8f, true); + break; + case 4: // Connected + Locations Scouted + Interface_DrawTextLine(this->state.gfxCtx, + SohFileSelect_GetArchipelagoSettingText(ASM_CONNECTED, language), statusPos, 175, + 120, 255, 120, textAlpha, 0.8f, true); + break; + } + + Gfx_SetupDL_39Opa(this->state.gfxCtx); + gDPSetCombineMode(POLY_OPA_DISP++, G_CC_MODULATEIA_PRIM, G_CC_MODULATEIA_PRIM); + gDPLoadTextureBlock(POLY_OPA_DISP++, gArrowCursorTex, G_IM_FMT_IA, G_IM_SIZ_8b, 16, 24, 0, + G_TX_NOMIRROR | G_TX_WRAP, G_TX_NOMIRROR | G_TX_WRAP, 4, G_TX_NOMASK, G_TX_NOLOD, + G_TX_NOLOD); + FileChoose_DrawTextRec(this->state.gfxCtx, this->stickRightPrompt.arrowColorR, + this->stickRightPrompt.arrowColorG, this->stickRightPrompt.arrowColorB, textAlpha, 62, + (85 + (this->archipelagoIndex * 16)), 0.42f, 0, 0, 1.0f, 1.0f); + } else if (this->configMode != CM_ROTATE_TO_NAME_ENTRY && this->configMode != CM_START_BOSS_RUSH_MENU && this->configMode != CM_ROTATE_TO_BOSS_RUSH_MENU && this->configMode != CM_BOSS_RUSH_TO_QUEST && this->configMode != CM_START_RANDOMIZER_SETTINGS_MENU && this->configMode != CM_ROTATE_TO_RANDOMIZER_SETTINGS_MENU && this->configMode != CM_RANDOMIZER_SETTINGS_MENU_TO_QUEST && - this->configMode != CM_NAME_ENTRY_TO_RANDOMIZER_SETTINGS_MENU) { + this->configMode != CM_NAME_ENTRY_TO_RANDOMIZER_SETTINGS_MENU && + this->configMode != CM_START_ARCHIPELAGO_SETTINGS_MENU && + this->configMode != CM_ROTATE_TO_ARCHIPELAGO_MENU && + this->configMode != CM_ARCHIPELAGO_SETTINGS_TO_QUEST && + this->configMode != CM_NAME_ENTRY_TO_ARCHIPELAGO_SETTINGS_MENU) { gDPPipeSync(POLY_OPA_DISP++); gDPSetPrimColor(POLY_OPA_DISP++, 0, 0, 255, 255, 255, this->titleAlpha[1]); gDPLoadTextureBlock(POLY_OPA_DISP++, sTitleLabels[gSaveContext.language][this->nextTitleLabel], G_IM_FMT_IA, @@ -2562,7 +2913,8 @@ void FileChoose_DrawWindowContents(GameState* thisx) { // Draw the small file name box instead when more meta info is enabled if (CVarGetInteger(CVAR_ENHANCEMENT("FileSelectMoreInfo"), 0) != 0 && - this->menuMode == FS_MENU_MODE_SELECT) { + this->menuMode == FS_MENU_MODE_SELECT && + Save_GetSaveMetaInfo(this->selectedFileIndex)->archiSave == 0) { // Location of file 1 small name box vertices gSPVertex(POLY_OPA_DISP++, &this->windowContentVtx[68], 4, 0); @@ -2629,7 +2981,7 @@ void FileChoose_DrawWindowContents(GameState* thisx) { gSP1Quadrangle(POLY_OPA_DISP++, 8, 10, 11, 9, 0); } - // draw rando label + // Draw rando label if (Save_GetSaveMetaInfo(i)->randoSave) { if (!FileChoose_IsSaveCompatible(Save_GetSaveMetaInfo(i))) { gDPSetPrimColor(POLY_OPA_DISP++, 0, 0, sWindowContentColors[1][0], sWindowContentColors[1][1], @@ -2660,6 +3012,21 @@ void FileChoose_DrawWindowContents(GameState* thisx) { G_TX_NOLOD, G_TX_NOLOD); gSP1Quadrangle(POLY_OPA_DISP++, 8, 10, 11, 9, 0); } + // Draw archipelago label + if (Save_GetSaveMetaInfo(i)->archiSave) { + if (!FileChoose_IsSaveCompatible(Save_GetSaveMetaInfo(i))) { + gDPSetPrimColor(POLY_OPA_DISP++, 0, 0, sWindowContentColors[1][0], sWindowContentColors[1][1], + sWindowContentColors[1][2], this->nameBoxAlpha[i]); + } else { + gDPSetPrimColor(POLY_OPA_DISP++, 0, 0, sWindowContentColors[isActive][0], + sWindowContentColors[isActive][1], sWindowContentColors[isActive][2], + this->nameAlpha[i]); + } + gDPLoadTextureBlock(POLY_OPA_DISP++, gFileSelArchiButtonTex, G_IM_FMT_IA, G_IM_SIZ_16b, 44, 16, 0, + G_TX_NOMIRROR | G_TX_WRAP, G_TX_NOMIRROR | G_TX_WRAP, G_TX_NOMASK, G_TX_NOMASK, + G_TX_NOLOD, G_TX_NOLOD); + gSP1Quadrangle(POLY_OPA_DISP++, 8, 10, 11, 9, 0); + } // draw connectors if (!FileChoose_IsSaveCompatible(Save_GetSaveMetaInfo(i)) && Save_GetSaveMetaInfo(i)->valid) { @@ -2675,7 +3042,7 @@ void FileChoose_DrawWindowContents(GameState* thisx) { G_TX_NOLOD, G_TX_NOLOD); gSP1Quadrangle(POLY_OPA_DISP++, 12, 14, 15, 13, 0); - if (this->n64ddFlags[i] || Save_GetSaveMetaInfo(i)->randoSave || + if (this->n64ddFlags[i] || Save_GetSaveMetaInfo(i)->randoSave || Save_GetSaveMetaInfo(i)->archiSave || Save_GetSaveMetaInfo(i)->requiresMasterQuest) { gSP1Quadrangle(POLY_OPA_DISP++, 16, 18, 19, 17, 0); } @@ -2794,7 +3161,9 @@ void FileChoose_ConfigModeDraw(GameState* thisx) { if (this->configMode != CM_NAME_ENTRY && this->configMode != CM_START_NAME_ENTRY && this->configMode != CM_QUEST_MENU && this->configMode != CM_NAME_ENTRY_TO_QUEST_MENU && this->configMode != CM_RANDOMIZER_SETTINGS_MENU && - this->configMode != CM_NAME_ENTRY_TO_RANDOMIZER_SETTINGS_MENU) { + this->configMode != CM_NAME_ENTRY_TO_RANDOMIZER_SETTINGS_MENU && + this->configMode != CM_ARCHIPELAGO_SETTINGS_MENU && + this->configMode != CM_NAME_ENTRY_TO_ARCHIPELAGO_SETTINGS_MENU) { gDPPipeSync(POLY_OPA_DISP++); gDPSetCombineMode(POLY_OPA_DISP++, G_CC_MODULATEIA_PRIM, G_CC_MODULATEIA_PRIM); gDPSetPrimColor(POLY_OPA_DISP++, 0, 0, this->windowColor[0], this->windowColor[1], this->windowColor[2], @@ -2903,7 +3272,10 @@ void FileChoose_ConfigModeDraw(GameState* thisx) { this->configMode == CM_ROTATE_TO_NAME_ENTRY || this->configMode == CM_QUEST_TO_MAIN || this->configMode == CM_NAME_ENTRY_TO_QUEST_MENU || this->configMode == CM_ROTATE_TO_BOSS_RUSH_MENU || this->configMode == CM_ROTATE_TO_RANDOMIZER_SETTINGS_MENU || - this->configMode == CM_NAME_ENTRY_TO_RANDOMIZER_SETTINGS_MENU) { + this->configMode == CM_ROTATE_TO_ARCHIPELAGO_MENU || + this->configMode == CM_NAME_ENTRY_TO_RANDOMIZER_SETTINGS_MENU || + this->configMode == CM_ARCHIPELAGO_SETTINGS_MENU || + this->configMode == CM_NAME_ENTRY_TO_ARCHIPELAGO_SETTINGS_MENU) { // window gDPPipeSync(POLY_OPA_DISP++); gDPSetCombineMode(POLY_OPA_DISP++, G_CC_MODULATEIA_PRIM, G_CC_MODULATEIA_PRIM); @@ -2936,7 +3308,9 @@ void FileChoose_ConfigModeDraw(GameState* thisx) { this->configMode == CM_START_BOSS_RUSH_MENU || this->configMode == CM_BOSS_RUSH_TO_QUEST || this->configMode == CM_RANDOMIZER_SETTINGS_MENU || this->configMode == CM_ROTATE_TO_RANDOMIZER_SETTINGS_MENU || this->configMode == CM_START_RANDOMIZER_SETTINGS_MENU || - this->configMode == CM_RANDOMIZER_SETTINGS_MENU_TO_QUEST) { + this->configMode == CM_RANDOMIZER_SETTINGS_MENU_TO_QUEST || + this->configMode == CM_START_ARCHIPELAGO_SETTINGS_MENU || this->configMode == CM_ARCHIPELAGO_SETTINGS_MENU || + this->configMode == CM_ROTATE_TO_ARCHIPELAGO_MENU || this->configMode == CM_ARCHIPELAGO_SETTINGS_TO_QUEST) { // window gDPPipeSync(POLY_OPA_DISP++); gDPSetCombineMode(POLY_OPA_DISP++, G_CC_MODULATEIA_PRIM, G_CC_MODULATEIA_PRIM); @@ -3262,6 +3636,7 @@ void FileChoose_LoadGame(GameState* thisx) { gSaveContext.naviTimer = 0; GameInteractor_ExecuteOnLoadGame(gSaveContext.fileNum); + GameInteractor_ExecutePostLoadGame(gSaveContext.fileNum); } static void (*gSelectModeUpdateFuncs[])(GameState*) = { @@ -3749,6 +4124,7 @@ void FileChoose_InitContext(GameState* thisx) { this->bossRushIndex = 0; this->bossRushOffset = 0; this->randomizerIndex = 0; + this->archipelagoIndex = 0; ShrinkWindow_SetVal(0); diff --git a/soh/src/overlays/gamestates/ovl_file_choose/z_file_nameset_PAL.c b/soh/src/overlays/gamestates/ovl_file_choose/z_file_nameset_PAL.c index d82ec38448f..308c7e7bdb5 100644 --- a/soh/src/overlays/gamestates/ovl_file_choose/z_file_nameset_PAL.c +++ b/soh/src/overlays/gamestates/ovl_file_choose/z_file_nameset_PAL.c @@ -379,6 +379,8 @@ void FileChoose_DrawNameEntry(GameState* thisx) { this->configMode = CM_NAME_ENTRY_TO_QUEST_MENU; } else if (this->prevConfigMode == CM_RANDOMIZER_SETTINGS_MENU) { this->configMode = CM_NAME_ENTRY_TO_RANDOMIZER_SETTINGS_MENU; + } else if (this->prevConfigMode = CM_ARCHIPELAGO_SETTINGS_MENU) { + this->configMode = CM_NAME_ENTRY_TO_ARCHIPELAGO_SETTINGS_MENU; } else { this->configMode = CM_NAME_ENTRY_TO_MAIN; } diff --git a/soh/src/overlays/misc/ovl_kaleido_scope/z_kaleido_item.c b/soh/src/overlays/misc/ovl_kaleido_scope/z_kaleido_item.c index dad520a0b48..ad16f4130b4 100644 --- a/soh/src/overlays/misc/ovl_kaleido_scope/z_kaleido_item.c +++ b/soh/src/overlays/misc/ovl_kaleido_scope/z_kaleido_item.c @@ -554,6 +554,8 @@ void KaleidoScope_DrawItemSelect(PlayState* play) { pauseCtx->cursorX[PAUSE_ITEM] = cursorX; pauseCtx->cursorY[PAUSE_ITEM] = cursorY; moveCursorResult = 1; + + GameInteractor_ExecuteOnKaleidoMoveCursorFromSpecialPos(pauseCtx, &cursorItem); break; } @@ -590,6 +592,8 @@ void KaleidoScope_DrawItemSelect(PlayState* play) { pauseCtx->cursorX[PAUSE_ITEM] = cursorX; pauseCtx->cursorY[PAUSE_ITEM] = cursorY; moveCursorResult = 1; + + GameInteractor_ExecuteOnKaleidoMoveCursorFromSpecialPos(pauseCtx, &cursorItem); break; } @@ -1192,6 +1196,21 @@ void KaleidoScope_UpdateItemEquip(PlayState* play) { } } + if (CVarGetInteger("gHoliday.Gameplay.BombArrows.Enabled", 0)) { + if (pauseCtx->equipTargetSlot == SLOT_BOW) { + CVarSetInteger("gHoliday.Gameplay.BombArrows.Active", 0); + } + u8 equipped_slot = gSaveContext.equips.cButtonSlots[pauseCtx->equipTargetCBtn]; + if (!CVarGetInteger("gHoliday.Gameplay.BombArrows.Active", 0) && + pauseCtx->equipTargetItem == ITEM_BOMB && equipped_slot == SLOT_BOW) { + CVarSetInteger("gHoliday.Gameplay.BombArrows.Active", 1); + pauseCtx->equipTargetItem = ITEM_BOW; + pauseCtx->equipTargetSlot = SLOT_BOW; + Audio_PlaySoundGeneral(NA_SE_SY_SET_FIRE_ARROW, &gSfxDefaultPos, 4, &gSfxDefaultFreqAndVolScale, + &gSfxDefaultFreqAndVolScale, &gSfxDefaultReverb); + } + } + // If the item is on another button already, swap the two uint16_t targetButtonIndex = pauseCtx->equipTargetCBtn + 1; for (uint16_t otherSlotIndex = 0; otherSlotIndex < ARRAY_COUNT(gSaveContext.equips.cButtonSlots);