diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 0607841922..37679e3f56 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -67,6 +67,7 @@ jobs: run: ci/build-appimage.sh - name: Upload artifacts + if: startsWith(github.event.ref, 'refs/tags/v') uses: actions/upload-artifact@v7 with: name: dusk-${{env.DUSK_VERSION}}-linux-${{matrix.preset}}-${{matrix.artifact_arch}} @@ -76,24 +77,27 @@ jobs: build-apple: name: Build Apple (${{matrix.name}}) - if: 'false' # TODO enable when CI is free - runs-on: macos-latest + runs-on: [self-hosted, macOS] strategy: fail-fast: false matrix: include: - - name: AppleClang macOS universal + - name: AppleClang macOS arm64 platform: macos - preset: x-macos-ci - artifact_name: macos-appleclang-universal - - name: AppleClang iOS arm64 - platform: ios - preset: x-ios-ci - artifact_name: ios-appleclang-arm64 - - name: AppleClang tvOS arm64 - platform: tvos - preset: x-tvos-ci - artifact_name: tvos-appleclang-arm64 + preset: x-macos-ci-arm64 + artifact_name: macos-appleclang-arm64 + # - name: AppleClang macOS x86_64 + # platform: macos + # preset: x-macos-ci-x86_64 + # artifact_name: macos-appleclang-x86_64 + # - name: AppleClang iOS arm64 + # platform: ios + # preset: x-ios-ci + # artifact_name: ios-appleclang-arm64 + # - name: AppleClang tvOS arm64 + # platform: tvos + # preset: x-tvos-ci + # artifact_name: tvos-appleclang-arm64 steps: - uses: actions/checkout@v6 @@ -101,22 +105,15 @@ jobs: fetch-depth: 0 submodules: recursive - - name: Update Homebrew - if: matrix.platform == 'tvos' - run: | - brew update - brew upgrade --formula - - name: Install dependencies + if: 'false' run: brew install cmake ninja - - name: Install markupsafe - if: matrix.platform == 'tvos' - run: pip3 install --break-system-packages markupsafe - - name: Install Rust iOS target if: matrix.platform == 'ios' - run: rustup target add aarch64-apple-ios + run: | + rustup toolchain install stable + rustup target add aarch64-apple-ios - name: Install Rust tvOS target if: matrix.platform == 'tvos' @@ -124,6 +121,12 @@ jobs: rustup toolchain install nightly rustup target add --toolchain nightly aarch64-apple-tvos + - name: Install Rust x86_64 macOS target + if: endsWith(matrix.preset, 'x86_64') + run: | + rustup toolchain install stable + rustup target add x86_64-apple-darwin + - name: Setup sccache uses: mozilla-actions/sccache-action@v0.0.9 @@ -134,6 +137,7 @@ jobs: run: cmake --build --preset ${{matrix.preset}} - name: Upload artifacts + if: startsWith(github.event.ref, 'refs/tags/v') uses: actions/upload-artifact@v7 with: name: dusk-${{env.DUSK_VERSION}}-${{matrix.artifact_name}} @@ -199,6 +203,7 @@ jobs: run: cmake --build --preset x-windows-ci-${{matrix.preset}} - name: Upload artifacts + if: startsWith(github.event.ref, 'refs/tags/v') uses: actions/upload-artifact@v7 with: name: dusk-${{env.DUSK_VERSION}}-win32-msvc-${{matrix.artifact_arch}} diff --git a/.vscode/launch.json b/.vscode/launch.json index 4cc435870a..7090699dd9 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -6,7 +6,7 @@ "type": "cppvsdbg", "request": "launch", "program": "${command:cmake.launchTargetPath}", - "args": ["-l", "1", "--dvd", "${workspaceRoot}/orig/GZ2E01/GZ2E01.iso"], + "args": ["-l", "1", "--dvd", "${workspaceRoot}/orig/GZ2E01/GZ2E01.iso", "--console"], "MIMode": "gdb", "miDebuggerPath": "gdb", "symbolSearchPath": "${command:cmake.launchTargetPath}", diff --git a/CMakeLists.txt b/CMakeLists.txt index 25d5d386f8..77cd0b9694 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -68,6 +68,9 @@ endif() message(STATUS "Dusk version set to ${DUSK_WC_DESCRIBE}") message(STATUS "Build type: ${CMAKE_BUILD_TYPE}") project(dusk LANGUAGES C CXX VERSION ${DUSK_VERSION_STRING}) +if (APPLE) + enable_language(OBJC) +endif () if (APPLE AND NOT TVOS AND CMAKE_SYSTEM_NAME STREQUAL tvOS) # ios.toolchain.cmake hack for SDL set(TVOS ON) @@ -125,19 +128,37 @@ if (DUSK_MOVIE_SUPPORT) else () set(_jpeg_lib ${_jpeg_install_dir}/lib/libturbojpeg.a) endif () + set(_jpeg_cmake_args + -DCMAKE_INSTALL_PREFIX=${_jpeg_install_dir} + -DENABLE_SHARED=OFF + -DWITH_TURBOJPEG=ON + -DWITH_JAVA=OFF + ) + if (CMAKE_TOOLCHAIN_FILE) + get_filename_component(_jpeg_toolchain_file "${CMAKE_TOOLCHAIN_FILE}" ABSOLUTE BASE_DIR "${CMAKE_SOURCE_DIR}") + list(APPEND _jpeg_cmake_args -DCMAKE_TOOLCHAIN_FILE=${_jpeg_toolchain_file}) + endif () + set(_jpeg_passthrough_vars + CMAKE_BUILD_TYPE + CMAKE_C_COMPILER + CMAKE_C_COMPILER_LAUNCHER + CMAKE_MAKE_PROGRAM + CMAKE_MSVC_RUNTIME_LIBRARY + CMAKE_OSX_ARCHITECTURES + DEPLOYMENT_TARGET + ENABLE_ARC + ENABLE_BITCODE + PLATFORM + ) + foreach(_var IN LISTS _jpeg_passthrough_vars) + if (DEFINED ${_var}) + list(APPEND _jpeg_cmake_args -D${_var}=${${_var}}) + endif () + endforeach () ExternalProject_Add(libjpeg-turbo-ext URL https://github.com/libjpeg-turbo/libjpeg-turbo/archive/refs/tags/3.1.0.tar.gz URL_HASH SHA256=35fec2e1ddfb05ecf6d93e50bc57c1e54bc81c16d611ddf6eff73fff266d8285 - CMAKE_ARGS - -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} - -DCMAKE_INSTALL_PREFIX=${_jpeg_install_dir} - -DCMAKE_TOOLCHAIN_FILE=${CMAKE_TOOLCHAIN_FILE} - -DCMAKE_C_COMPILER=${CMAKE_C_COMPILER} - -DCMAKE_C_COMPILER_LAUNCHER=${CMAKE_C_COMPILER_LAUNCHER} - -DCMAKE_MSVC_RUNTIME_LIBRARY=${CMAKE_MSVC_RUNTIME_LIBRARY} - -DENABLE_SHARED=OFF - -DWITH_TURBOJPEG=ON - -DWITH_JAVA=OFF + CMAKE_ARGS ${_jpeg_cmake_args} BUILD_BYPRODUCTS ${_jpeg_lib} ) file(MAKE_DIRECTORY ${_jpeg_install_dir}/include) @@ -249,7 +270,7 @@ include(files.cmake) # TODO: version handling for res includes set(DUSK_BUNDLE_NAME Dusk) -set(DUSK_BUNDLE_IDENTIFIER dev.decomp.dusk) +set(DUSK_BUNDLE_IDENTIFIER dev.twilitrealm.dusk) set(DUSK_COMPANY_NAME "Twilit Realm") set(DUSK_FILE_DESCRIPTION "Dusk") set(DUSK_PRODUCT_NAME "Dusk") @@ -277,7 +298,7 @@ set(GAME_INCLUDE_DIRS ${CMAKE_BINARY_DIR}) set(GAME_LIBS aurora::core aurora::gx aurora::gd aurora::si aurora::vi aurora::pad aurora::mtx aurora::os aurora::dvd - aurora::card freeverb cxxopts::cxxopts absl::flat_hash_map nlohmann_json::nlohmann_json TracyClient) + aurora::card freeverb cxxopts::cxxopts absl::flat_hash_map nlohmann_json::nlohmann_json TracyClient fmt::fmt) list(APPEND GAME_LIBS libzstd_static) @@ -295,9 +316,51 @@ if (DUSK_MOVIE_SUPPORT) list(APPEND GAME_COMPILE_DEFS MOVIE_SUPPORT=1) endif () +option(DUSK_ENABLE_DISCORD_RPC "Enable Discord Rich Presence support" ON) +if (DUSK_ENABLE_DISCORD_RPC AND NOT ANDROID AND NOT IOS AND NOT TVOS) + + FetchContent_Populate(discord_rpc + URL https://github.com/discord/discord-rpc/archive/refs/tags/v3.4.0.tar.gz + URL_HASH SHA256=e13427019027acd187352dacba6c65953af66fdf3c35fcf38fc40b454a9d7855 + DOWNLOAD_EXTRACT_TIMESTAMP TRUE + ) + # RapidJSON is a git submodule absent from the discord-rpc tarball; fetch separately. + FetchContent_Populate(rapidjson + URL https://github.com/Tencent/rapidjson/archive/refs/tags/v1.1.0.tar.gz + URL_HASH SHA256=bf7ced29704a1e696fbccf2a2b4ea068e7774fa37f6d7dd4039d0787f8bed98e + DOWNLOAD_EXTRACT_TIMESTAMP TRUE + ) + + if (NOT TARGET discord-rpc) + set(_drpc ${discord_rpc_SOURCE_DIR}/src) + set(_drpc_src + ${_drpc}/discord_rpc.cpp + ${_drpc}/rpc_connection.cpp + ${_drpc}/serialization.cpp + ) + if (WIN32) + list(APPEND _drpc_src ${_drpc}/connection_win.cpp ${_drpc}/discord_register_win.cpp) + elseif (APPLE) + list(APPEND _drpc_src ${_drpc}/connection_unix.cpp ${_drpc}/discord_register_osx.m) + else () + list(APPEND _drpc_src ${_drpc}/connection_unix.cpp ${_drpc}/discord_register_linux.cpp) + endif () + add_library(discord-rpc STATIC ${_drpc_src}) + target_include_directories(discord-rpc PUBLIC + ${discord_rpc_SOURCE_DIR}/include + ${rapidjson_SOURCE_DIR}/include + ) + if (UNIX) + target_link_libraries(discord-rpc PUBLIC pthread) + endif () + endif () + list(APPEND GAME_LIBS discord-rpc) + list(APPEND GAME_COMPILE_DEFS DUSK_DISCORD_RPC=1) +endif () + # Edit & Continue if (MSVC) - if (CMAKE_MSVC_DEBUG_INFORMATION_FORMAT STREQUAL "" AND CMAKE_BUILD_TYPE STREQUAL Debug) + if ("${CMAKE_MSVC_DEBUG_INFORMATION_FORMAT}" STREQUAL "" AND CMAKE_BUILD_TYPE STREQUAL "Debug") set(CMAKE_MSVC_DEBUG_INFORMATION_FORMAT "EditAndContinue") endif () if (CMAKE_MSVC_DEBUG_INFORMATION_FORMAT STREQUAL "EditAndContinue") @@ -336,12 +399,6 @@ target_include_directories(game_base PRIVATE ${GAME_INCLUDE_DIRS}) target_link_libraries(game_debug PRIVATE ${GAME_LIBS}) target_link_libraries(game_base PRIVATE ${GAME_LIBS}) -# Combined game library -add_library(game STATIC - $ - $) -target_link_libraries(game PUBLIC ${GAME_LIBS}) - if(ANDROID) add_library(dusk SHARED src/dusk/main.cpp) set_target_properties(dusk PROPERTIES OUTPUT_NAME main) @@ -351,7 +408,7 @@ endif () target_compile_definitions(dusk PRIVATE TARGET_PC AVOID_UB=1 VERSION=0) target_include_directories(dusk PRIVATE include) -target_link_libraries(dusk PRIVATE game aurora::main) +target_link_libraries(dusk PRIVATE game_base game_debug aurora::main) if (TARGET crashpad_handler) add_dependencies(dusk crashpad_handler) endif () @@ -362,12 +419,14 @@ if (ANDROID) target_link_options(dusk PRIVATE "-Wl,-u,SDL_main") endif () -add_custom_command(TARGET dusk POST_BUILD - COMMAND ${CMAKE_COMMAND} -E copy_directory - "${CMAKE_SOURCE_DIR}/res" - "$/res" - COMMENT "Copying resources" -) +if (NOT APPLE) + add_custom_command(TARGET dusk POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy_directory + "${CMAKE_SOURCE_DIR}/res" + "$/res" + COMMENT "Copying resources" + ) +endif () if (WIN32) set(DUSK_WINDOWS_RESOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/platforms/windows) @@ -401,44 +460,49 @@ endif () if (APPLE) if (IOS) set(DUSK_RESOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios) - set(DUSK_INFO_PLIST ${DUSK_RESOURCE_DIR}/Info.plist.in) - file(GLOB_RECURSE DUSK_RESOURCE_FILES "${DUSK_RESOURCE_DIR}/Base.lproj/*") - endif () - if (IOS OR TVOS) - target_sources(dusk PRIVATE ${DUSK_RESOURCE_FILES}) - foreach (FILE ${DUSK_RESOURCE_FILES}) - file(RELATIVE_PATH NEW_FILE "${DUSK_RESOURCE_DIR}" ${FILE}) - get_filename_component(NEW_FILE_PATH ${NEW_FILE} DIRECTORY) - set_property(SOURCE ${FILE} PROPERTY MACOSX_PACKAGE_LOCATION "Resources/${NEW_FILE_PATH}") - endforeach () - set_target_properties( - dusk PROPERTIES - MACOSX_BUNDLE TRUE - MACOSX_BUNDLE_BUNDLE_NAME ${DUSK_BUNDLE_NAME} - MACOSX_BUNDLE_GUI_IDENTIFIER ${DUSK_BUNDLE_IDENTIFIER} - MACOSX_BUNDLE_BUNDLE_VERSION ${DUSK_VERSION_STRING} - MACOSX_BUNDLE_SHORT_VERSION_STRING ${DUSK_SHORT_VERSION_STRING} - OUTPUT_NAME dusk - XCODE_ATTRIBUTE_CODE_SIGNING_ALLOWED "YES" - XCODE_ATTRIBUTE_CODE_SIGNING_REQUIRED "YES" - ) - if (CMAKE_GENERATOR STREQUAL "Xcode") - set_target_properties(dusk PROPERTIES MACOSX_BUNDLE_INFO_PLIST ${DUSK_INFO_PLIST}) - elseif (DEFINED DUSK_INFO_PLIST) - set(MACOSX_BUNDLE_EXECUTABLE_NAME dusk) - set(MACOSX_BUNDLE_GUI_IDENTIFIER ${DUSK_BUNDLE_IDENTIFIER}) - set(MACOSX_BUNDLE_BUNDLE_NAME ${DUSK_BUNDLE_NAME}) - set(MACOSX_BUNDLE_BUNDLE_VERSION ${DUSK_VERSION_STRING}) - set(MACOSX_BUNDLE_SHORT_VERSION_STRING ${DUSK_SHORT_VERSION_STRING}) - set(DUSK_GENERATED_INFO_PLIST ${CMAKE_CURRENT_BINARY_DIR}/dusk.Info.plist) - configure_file(${DUSK_INFO_PLIST} ${DUSK_GENERATED_INFO_PLIST}) - add_custom_command( - TARGET dusk POST_BUILD - COMMAND ${CMAKE_COMMAND} -E copy_if_different ${DUSK_GENERATED_INFO_PLIST} $/Info.plist - VERBATIM - ) - endif () + elseif (TVOS) + set(DUSK_RESOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/platforms/tvos) + else () + set(DUSK_RESOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/platforms/macos) endif () + set(DUSK_INFO_PLIST ${DUSK_RESOURCE_DIR}/Info.plist.in) + file(GLOB_RECURSE DUSK_RESOURCE_FILES + "${DUSK_RESOURCE_DIR}/Assets.car" + "${DUSK_RESOURCE_DIR}/Base.lproj/*" + "${DUSK_RESOURCE_DIR}/Dusk.icns") + file(GLOB_RECURSE DUSK_APP_RESOURCE_FILES "${CMAKE_CURRENT_SOURCE_DIR}/res/*") + target_sources(dusk PRIVATE ${DUSK_RESOURCE_FILES}) + target_sources(dusk PRIVATE ${DUSK_APP_RESOURCE_FILES}) + foreach (FILE ${DUSK_RESOURCE_FILES}) + file(RELATIVE_PATH NEW_FILE "${DUSK_RESOURCE_DIR}" ${FILE}) + get_filename_component(NEW_FILE_PATH ${NEW_FILE} DIRECTORY) + set_property(SOURCE ${FILE} PROPERTY MACOSX_PACKAGE_LOCATION "Resources/${NEW_FILE_PATH}") + endforeach () + foreach (FILE ${DUSK_APP_RESOURCE_FILES}) + file(RELATIVE_PATH NEW_FILE "${CMAKE_CURRENT_SOURCE_DIR}" ${FILE}) + get_filename_component(NEW_FILE_PATH ${NEW_FILE} DIRECTORY) + set_property(SOURCE ${FILE} PROPERTY MACOSX_PACKAGE_LOCATION "Resources/${NEW_FILE_PATH}") + endforeach () + set_target_properties( + dusk PROPERTIES + MACOSX_BUNDLE TRUE + MACOSX_BUNDLE_BUNDLE_NAME ${DUSK_BUNDLE_NAME} + MACOSX_BUNDLE_GUI_IDENTIFIER ${DUSK_BUNDLE_IDENTIFIER} + MACOSX_BUNDLE_BUNDLE_VERSION ${DUSK_VERSION_STRING} + MACOSX_BUNDLE_SHORT_VERSION_STRING ${DUSK_SHORT_VERSION_STRING} + MACOSX_BUNDLE_INFO_PLIST ${DUSK_INFO_PLIST} + OUTPUT_NAME Dusk + XCODE_ATTRIBUTE_CODE_SIGNING_ALLOWED "YES" + XCODE_ATTRIBUTE_CODE_SIGNING_REQUIRED "YES" + ) +endif () + +if (IOS) + find_library(UIKIT_FRAMEWORK UIKit REQUIRED) + find_library(UNIFORM_TYPE_IDENTIFIERS_FRAMEWORK UniformTypeIdentifiers REQUIRED) + target_sources(dusk PRIVATE src/dusk/ios/FileSelectDialog.m) + set_source_files_properties(src/dusk/ios/FileSelectDialog.m PROPERTIES COMPILE_FLAGS -fobjc-arc) + target_link_libraries(dusk PRIVATE ${UIKIT_FRAMEWORK} ${UNIFORM_TYPE_IDENTIFIERS_FRAMEWORK}) endif () include(extern/aurora/cmake/AuroraCopyRuntimeDLLs.cmake) @@ -487,7 +551,9 @@ if (TARGET crashpad_handler) endif () install(TARGETS ${BINARY_TARGETS} ${EXTRA_TARGETS} DESTINATION ${CMAKE_INSTALL_PREFIX}) aurora_install_runtime_dlls(dusk ${CMAKE_INSTALL_PREFIX}) -install(DIRECTORY ${CMAKE_SOURCE_DIR}/res DESTINATION ${CMAKE_INSTALL_PREFIX}) +if (NOT APPLE) + install(DIRECTORY ${CMAKE_SOURCE_DIR}/res DESTINATION ${CMAKE_INSTALL_PREFIX}) +endif () if (CMAKE_BUILD_TYPE STREQUAL Debug OR CMAKE_BUILD_TYPE STREQUAL RelWithDebInfo) set(DEBUG_FILES_LIST "") foreach (target IN LISTS BINARY_TARGETS EXTRA_TARGETS) @@ -509,18 +575,22 @@ if (CMAKE_BUILD_TYPE STREQUAL Debug OR CMAKE_BUILD_TYPE STREQUAL RelWithDebInfo) endif () list(APPEND DEBUG_FILES_LIST "${output_name}") endforeach () - if (WIN32) - list(TRANSFORM DEBUG_FILES_LIST APPEND ".pdb") - list(JOIN DEBUG_FILES_LIST " " DEBUG_FILES) - install(CODE "execute_process(WORKING_DIRECTORY \"${CMAKE_INSTALL_PREFIX}\" COMMAND 7z a -t7z \"${CMAKE_INSTALL_PREFIX}/debug.7z\" ${DEBUG_FILES})") - elseif (APPLE) - list(TRANSFORM DEBUG_FILES_LIST APPEND ".dSYM") - list(JOIN DEBUG_FILES_LIST " " DEBUG_FILES) - install(CODE "execute_process(WORKING_DIRECTORY \"${CMAKE_INSTALL_PREFIX}\" COMMAND tar acfv \"${CMAKE_INSTALL_PREFIX}/debug.tar.xz\" ${DEBUG_FILES})") - elseif (UNIX) - list(TRANSFORM DEBUG_FILES_LIST APPEND ".dbg") - list(JOIN DEBUG_FILES_LIST " " DEBUG_FILES) - install(CODE "execute_process(WORKING_DIRECTORY \"${CMAKE_INSTALL_PREFIX}\" COMMAND tar -I \"xz -9 -T0\" -cvf \"${CMAKE_INSTALL_PREFIX}/debug.tar.xz\" ${DEBUG_FILES})") + # This is a terrible hack to only run this on CI + # until I turn this into a script or something + if(DEFINED ENV{GITHUB_ENV}) + if (WIN32) + list(TRANSFORM DEBUG_FILES_LIST APPEND ".pdb") + list(JOIN DEBUG_FILES_LIST " " DEBUG_FILES) + install(CODE "execute_process(WORKING_DIRECTORY \"${CMAKE_INSTALL_PREFIX}\" COMMAND 7z a -t7z \"${CMAKE_INSTALL_PREFIX}/debug.7z\" ${DEBUG_FILES})") + elseif (APPLE) + list(TRANSFORM DEBUG_FILES_LIST APPEND ".dSYM") + list(JOIN DEBUG_FILES_LIST " " DEBUG_FILES) + install(CODE "execute_process(WORKING_DIRECTORY \"${CMAKE_INSTALL_PREFIX}\" COMMAND tar acfv \"${CMAKE_INSTALL_PREFIX}/debug.tar.xz\" ${DEBUG_FILES})") + elseif (UNIX) + list(TRANSFORM DEBUG_FILES_LIST APPEND ".dbg") + list(JOIN DEBUG_FILES_LIST " " DEBUG_FILES) + install(CODE "execute_process(WORKING_DIRECTORY \"${CMAKE_INSTALL_PREFIX}\" COMMAND tar -I \"xz -9 -T0\" -cvf \"${CMAKE_INSTALL_PREFIX}/debug.tar.xz\" ${DEBUG_FILES})") + endif () endif () endif () foreach (target IN LISTS BINARY_TARGETS) diff --git a/CMakePresets.json b/CMakePresets.json index 44e75973b9..92799249a9 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -148,8 +148,7 @@ "cacheVariables": { "CMAKE_C_COMPILER": "cl", "CMAKE_CXX_COMPILER": "cl", - "CMAKE_INSTALL_PREFIX": "${sourceDir}/build/install", - "AURORA_DAWN_PROVIDER": "vendor" + "CMAKE_INSTALL_PREFIX": "${sourceDir}/build/install" }, "vendor": { "microsoft.com/VisualStudioSettings/CMake/1.0": { @@ -359,7 +358,10 @@ "inherits": [ "relwithdebinfo", "ci" - ] + ], + "cacheVariables": { + "AURORA_SDL3_PROVIDER": "vendor" + } }, { "name": "x-linux-ci-gcc", @@ -380,7 +382,39 @@ "inherits": [ "macos-default-relwithdebinfo", "ci" - ] + ], + "cacheVariables": { + "AURORA_NOD_PROVIDER": "vendor", + "CMAKE_DISABLE_FIND_PACKAGE_PkgConfig": { + "type": "BOOL", + "value": true + }, + "CMAKE_OSX_DEPLOYMENT_TARGET": "11.0", + "CMAKE_IGNORE_PREFIX_PATH": "/opt/homebrew", + "DUSK_MOVIE_SUPPORT": { + "type": "BOOL", + "value": false + } + } + }, + { + "name": "x-macos-ci-arm64", + "inherits": [ + "x-macos-ci" + ], + "cacheVariables": { + "CMAKE_OSX_ARCHITECTURES": "arm64" + } + }, + { + "name": "x-macos-ci-x86_64", + "inherits": [ + "x-macos-ci" + ], + "cacheVariables": { + "CMAKE_OSX_ARCHITECTURES": "x86_64", + "Rust_CARGO_TARGET": "x86_64-apple-darwin" + } }, { "name": "x-ios-ci", @@ -389,7 +423,11 @@ ], "cacheVariables": { "CMAKE_C_COMPILER_LAUNCHER": "sccache", - "CMAKE_CXX_COMPILER_LAUNCHER": "sccache" + "CMAKE_CXX_COMPILER_LAUNCHER": "sccache", + "DUSK_MOVIE_SUPPORT": { + "type": "BOOL", + "value": false + } } }, { @@ -399,7 +437,11 @@ ], "cacheVariables": { "CMAKE_C_COMPILER_LAUNCHER": "sccache", - "CMAKE_CXX_COMPILER_LAUNCHER": "sccache" + "CMAKE_CXX_COMPILER_LAUNCHER": "sccache", + "DUSK_MOVIE_SUPPORT": { + "type": "BOOL", + "value": false + } } }, { @@ -556,10 +598,19 @@ ] }, { - "name": "x-macos-ci", - "configurePreset": "x-macos-ci", - "description": "(Internal) macOS CI", - "displayName": "(Internal) macOS CI", + "name": "x-macos-ci-arm64", + "configurePreset": "x-macos-ci-arm64", + "description": "(Internal) macOS CI arm64", + "displayName": "(Internal) macOS CI arm64", + "targets": [ + "install" + ] + }, + { + "name": "x-macos-ci-x86_64", + "configurePreset": "x-macos-ci-x86_64", + "description": "(Internal) macOS CI x86_64", + "displayName": "(Internal) macOS CI x86_64", "targets": [ "install" ] @@ -567,8 +618,8 @@ { "name": "x-ios-ci", "configurePreset": "x-ios-ci", - "description": "(Internal) iOS CI", - "displayName": "(Internal) iOS CI", + "description": "(Internal) iOS CI arm64", + "displayName": "(Internal) iOS CI arm64", "targets": [ "install" ] @@ -576,8 +627,8 @@ { "name": "x-tvos-ci", "configurePreset": "x-tvos-ci", - "description": "(Internal) tvOS CI", - "displayName": "(Internal) tvOS CI", + "description": "(Internal) tvOS CI arm64", + "displayName": "(Internal) tvOS CI arm64", "targets": [ "install" ] diff --git a/README.md b/README.md index c41985d394..7322441b74 100644 --- a/README.md +++ b/README.md @@ -1,103 +1,59 @@ -## Dusk +![DuskLogo](res/logo-mascot.webp) -### Building -#### Prerequisites -* [CMake 3.25+](https://cmake.org) - * Windows: Install `CMake Tools` in Visual Studio - * macOS: `brew install cmake` -* [Python 3+](https://python.org) - * Windows: [Microsoft Store](https://go.microsoft.com/fwlink?linkID=2082640) - * Verify it's added to `%PATH%` by typing `python` in `cmd`. - * macOS: `brew install python@3` -* **[Windows]** [Visual Studio 2026 Community](https://www.visualstudio.com/en-us/products/visual-studio-community-vs.aspx) - * Select `C++ Development` and verify the following packages are included: - * `Windows 11 SDK` - * `CMake Tools` - * `C++ Clang Compiler` - * `C++ Clang-cl` -* **[macOS]** [Xcode 16.4+](https://developer.apple.com/xcode/download/) -* **[Linux]** Actively tested on Ubuntu 24.04, Arch Linux & derivatives. - * Ubuntu 24.04+ packages - ``` - build-essential curl git ninja-build clang lld zlib1g-dev libcurl4-openssl-dev \ - libglu1-mesa-dev libdbus-1-dev libvulkan-dev libxi-dev libxrandr-dev libasound2-dev libpulse-dev \ - libudev-dev libpng-dev libncurses5-dev cmake libx11-xcb-dev python3 python-is-python3 \ - libclang-dev libfreetype-dev libxinerama-dev libxcursor-dev python3-markupsafe libgtk-3-dev \ - libxss-dev libxtst-dev - ``` - * Arch Linux packages - ``` - base-devel cmake ninja llvm vulkan-headers python python-markupsafe clang lld alsa-lib libpulse libxrandr freetype2 - ``` - * Fedora packages - ``` - cmake vulkan-headers ninja-build clang-devel llvm-devel libpng-devel - ``` - * It's also important that you install the developer tools and libraries - ``` - sudo dnf groupinstall "Development Tools" "Development Libraries" - ``` -#### Setup -Clone and initialize the Dusk repository -```sh -git clone --recursive https://github.com/TwilitRealm/dusk.git -cd dusk -git pull -git submodule update --init --recursive -``` +- ### **[Official Website](https://twilitrealm.dev)** +- ### **[Discord](https://discord.gg/QACynxeyna)** -#### Building +# Setup +**⚠️Dusk does NOT provide any copyrighted assets. You must provide your own copy of the game.** -**CLion (Windows / macOS / Linux)** +### 1. Verify your ROM dump +First make sure your dump of the game is clean and supported by Dusk. You can do this by checking the sha1 hash of your dump against this list of supported versions. -Open the project directory in CLion. Enable the appropriate presets for your platform: +| Version | sha1 hash | +| ------------ | ---------------------------------------- | +| GameCube USA | 75edd3ddff41f125d1b4ce1a40378f1b565519e7 | -![CLion](assets/clion.png) +### 2. Download [Dusk](https://github.com/TwilitRealm/dusk/releases) -**Visual Studio (Windows)** +### 3. Setup the game +#### Windows +- Extract the zip folder +- Place your dump of the game into the same folder where you extracted to +- Launch `dusk.exe` -Open the project directory in Visual Studio. The CMake configuration will be loaded automatically. +#### macOS +- TODO -**ninja (macOS)** +#### Linux +- TODO -```sh -cmake --preset macos-default-relwithdebinfo -cmake --build --preset macos-default-relwithdebinfo -``` +#### iOS +- TODO -Alternate presets available: -- `macos-default-debug`: Clang, Debug +#### android +- TODO -**ninja (Linux)** +# Building +If you'd like to build Dusk from source, please read the [build instructions](docs/building.md). -```sh -cmake --preset linux-default-relwithdebinfo -cmake --build --preset linux-default-relwithdebinfo -``` +# Credits +- Taka +- encounter +- Antidote +- caseif +- CraftyBoss +- crowell +- dooplecks +- gymnast86 +- Irastris +- kipcode66 +- Lars +- LunarSoap +- Maddie +- MelonSpeedruns +- Pheenoh +- PJB +- Roeming +- YunataSavior -Alternate presets available: -- `linux-default-debug`: GCC, Debug -- `linux-clang-relwithdebinfo`: Clang, RelWithDebInfo -- `linux-clang-debug`: Clang, Debug - -**ninja (Windows)** - -```sh -cmake --preset windows-msvc-relwithdebinfo -cmake --build --preset windows-msvc-relwithdebinfo -``` - -Alternate presets available: -- `windows-msvc-debug`: MSVC, Debug -- `windows-clang-relwithdebinfo`: Clang-cl, RelWithDebInfo -- `windows-clang-debug`: Clang-cl, Debug - -#### Running -Pass the disc image as a positional argument. Supported formats: ISO (GCM), RVZ, WIA, WBFS, CISO, GCZ -```sh -build/{preset}/dusk /path/to/game.rvz -``` -If no path is specified, Dusk defaults to `game.iso` in the current working directory. - -#### 30 FPS on Debug -When compiled fully in a Debug the game runs too slowly to hit playable 30 FPS. To avoid this, you can set a CMake cache variable to optimize specific critical files without hampering debuggability in the rest of the program: `-DDUSK_SELECTED_OPT=ON`. When building for MSVC (Windows) you must also modify `CMAKE_CXX_FLAGS_DEBUG` and `CMAKE_C_FLAGS_DEBUG` to remove `/RTC1` from the flags, like so: `-DCMAKE_CXX_FLAGS_DEBUG="/MDd /Zi /Ob0 /Od" -DCMAKE_C_FLAGS_DEBUG="/MDd /Zi /Ob0 /Od"` +Special thanks to the TP Decomp team, the GC/Wii Decomp community, the Aurora developers, and the TP speedrunning community. diff --git a/docs/building.md b/docs/building.md new file mode 100644 index 0000000000..0b1befa49f --- /dev/null +++ b/docs/building.md @@ -0,0 +1,98 @@ +### Building +#### Prerequisites +* [CMake 3.25+](https://cmake.org) + * Windows: Install `CMake Tools` in Visual Studio + * macOS: `brew install cmake` +* [Python 3+](https://python.org) + * Windows: [Microsoft Store](https://go.microsoft.com/fwlink?linkID=2082640) + * Verify it's added to `%PATH%` by typing `python` in `cmd`. + * macOS: `brew install python@3` +* **[Windows]** [Visual Studio 2026 Community](https://www.visualstudio.com/en-us/products/visual-studio-community-vs.aspx) + * Select `C++ Development` and verify the following packages are included: + * `Windows 11 SDK` + * `CMake Tools` + * `C++ Clang Compiler` + * `C++ Clang-cl` +* **[macOS]** [Xcode 16.4+](https://developer.apple.com/xcode/download/) +* **[Linux]** Actively tested on Ubuntu 24.04, Arch Linux & derivatives. + * Ubuntu 24.04+ packages + ``` + build-essential curl git ninja-build clang lld zlib1g-dev libcurl4-openssl-dev \ + libglu1-mesa-dev libdbus-1-dev libvulkan-dev libxi-dev libxrandr-dev libasound2-dev libpulse-dev \ + libudev-dev libpng-dev libncurses5-dev cmake libx11-xcb-dev python3 python-is-python3 \ + libclang-dev libfreetype-dev libxinerama-dev libxcursor-dev python3-markupsafe libgtk-3-dev \ + libxss-dev libxtst-dev + ``` + * Arch Linux packages + ``` + base-devel cmake ninja llvm vulkan-headers python python-markupsafe clang lld alsa-lib libpulse libxrandr freetype2 + ``` + * Fedora packages + ``` + cmake vulkan-headers ninja-build clang-devel llvm-devel libpng-devel + ``` + * It's also important that you install the developer tools and libraries + ``` + sudo dnf groupinstall "Development Tools" "Development Libraries" + ``` +#### Setup +Clone and initialize the Dusk repository +```sh +git clone --recursive https://github.com/TwilitRealm/dusk.git +cd dusk +git pull +git submodule update --init --recursive +``` + +#### Building + +**CLion (Windows / macOS / Linux)** + +Open the project directory in CLion. Enable the appropriate presets for your platform: + +![CLion](../assets/clion.png) + +**Visual Studio (Windows)** + +Open the project directory in Visual Studio. The CMake configuration will be loaded automatically. + +**ninja (macOS)** + +```sh +cmake --preset macos-default-relwithdebinfo +cmake --build --preset macos-default-relwithdebinfo +``` + +Alternate presets available: +- `macos-default-debug`: Clang, Debug + +**ninja (Linux)** + +```sh +cmake --preset linux-default-relwithdebinfo +cmake --build --preset linux-default-relwithdebinfo +``` + +Alternate presets available: +- `linux-default-debug`: GCC, Debug +- `linux-clang-relwithdebinfo`: Clang, RelWithDebInfo +- `linux-clang-debug`: Clang, Debug + +**ninja (Windows)** + +```sh +cmake --preset windows-msvc-relwithdebinfo +cmake --build --preset windows-msvc-relwithdebinfo +``` + +Alternate presets available: +- `windows-msvc-debug`: MSVC, Debug +- `windows-clang-relwithdebinfo`: Clang-cl, RelWithDebInfo +- `windows-clang-debug`: Clang-cl, Debug + +#### Running +Pass the disc image as a positional argument. Supported formats: ISO (GCM), RVZ, WIA, WBFS, CISO, GCZ +```sh +build/{preset}/dusk /path/to/game.rvz +``` +If no path is specified, Dusk defaults to `game.iso` in the current working directory. diff --git a/extern/aurora b/extern/aurora index 4d7ff2ac11..6d69b7822e 160000 --- a/extern/aurora +++ b/extern/aurora @@ -1 +1 @@ -Subproject commit 4d7ff2ac115409192ab17e45771c739ea004cef1 +Subproject commit 6d69b7822e95ad9e82537848c968058be4fbbca5 diff --git a/files.cmake b/files.cmake index d1e0436625..c50cac2d8c 100644 --- a/files.cmake +++ b/files.cmake @@ -15,7 +15,6 @@ set(DOLZEL_FILES src/m_Do/m_Do_DVDError.cpp src/m_Do/m_Do_MemCard.cpp src/m_Do/m_Do_MemCardRWmng.cpp - src/m_Do/m_Do_machine_exception.cpp src/m_Do/m_Do_hostIO.cpp src/c/c_damagereaction.cpp src/c/c_dylink.cpp @@ -1334,6 +1333,7 @@ set(DUSK_FILES include/dusk/endian_gx.hpp include/dusk/config.hpp include/dusk/dvd_asset.hpp + include/dusk/scope_guard.hpp src/dusk/dvd_asset.cpp src/d/actor/d_a_alink_dusk.cpp src/dusk/asserts.cpp @@ -1342,9 +1342,12 @@ set(DUSK_FILES src/dusk/endian.cpp src/dusk/extras.c src/dusk/extras.cpp + src/dusk/file_select.cpp + src/dusk/file_select.hpp src/dusk/frame_interpolation.cpp + src/dusk/game_clock.cpp src/dusk/globals.cpp - src/dusk/gyro_aim.cpp + src/dusk/gyro.cpp src/dusk/io.cpp src/dusk/layout.cpp src/dusk/logging.cpp @@ -1358,10 +1361,10 @@ set(DUSK_FILES src/dusk/imgui/ImGuiEngine.hpp src/dusk/imgui/ImGuiMenuGame.cpp src/dusk/imgui/ImGuiMenuGame.hpp + src/dusk/imgui/ImGuiBloomWindow.cpp + src/dusk/imgui/ImGuiBloomWindow.hpp src/dusk/imgui/ImGuiMenuTools.cpp src/dusk/imgui/ImGuiMenuTools.hpp - src/dusk/imgui/ImGuiMenuEnhancements.cpp - src/dusk/imgui/ImGuiMenuEnhancements.hpp src/dusk/imgui/ImGuiPreLaunchWindow.cpp src/dusk/imgui/ImGuiPreLaunchWindow.hpp src/dusk/imgui/ImGuiFirstRunPreset.hpp @@ -1381,4 +1384,5 @@ set(DUSK_FILES src/dusk/OSContext.cpp src/dusk/OSThread.cpp src/dusk/OSMutex.cpp + src/dusk/discord_presence.cpp ) diff --git a/include/d/actor/d_a_alink.h b/include/d/actor/d_a_alink.h index 786c0a4555..ed4f7f59bc 100644 --- a/include/d/actor/d_a_alink.h +++ b/include/d/actor/d_a_alink.h @@ -4552,7 +4552,7 @@ public: void handleGamepadColor(); void handleWolfHowl(); void handleQuickTransform(); - bool checkGyroAimItemContext(); + bool checkGyroAimContext(); #endif }; // Size: 0x385C diff --git a/include/d/actor/d_a_e_wb.h b/include/d/actor/d_a_e_wb.h index dd36474025..ba680366fb 100644 --- a/include/d/actor/d_a_e_wb.h +++ b/include/d/actor/d_a_e_wb.h @@ -220,6 +220,15 @@ public: /* 0x17E2 */ s16 wait_roll_angle; ///< @brief Roll angle during wait state. /* 0x17E4 */ u8 field_0x17e4[0x17e8 - 0x17e4]; /* 0x17E8 */ f32 ride_speed_max; ///< @brief Speed rate for riding calculations. +#if TARGET_PC + cXyz himo_mat_interp_prev[2][16]; + cXyz himo_mat_interp_curr[2][16]; + cXyz himo_tex_interp_prev[2]; + cXyz himo_tex_interp_curr[2]; + bool himo_interp_prev_valid; + bool himo_interp_curr_valid; + s8 demo_cam_sync_ticks; +#endif }; STATIC_ASSERT(sizeof(e_wb_class) == 0x17EC); diff --git a/include/d/actor/d_a_mirror.h b/include/d/actor/d_a_mirror.h index fd69c8682e..06c5603899 100644 --- a/include/d/actor/d_a_mirror.h +++ b/include/d/actor/d_a_mirror.h @@ -25,6 +25,9 @@ public: /* 0x164 */ cXyz mMinVal; /* 0x170 */ cXyz mMaxVal; /* 0x17C */ cXyz mViewScale; +#if TARGET_PC + bool mbReset = false; +#endif }; /** diff --git a/include/d/actor/d_a_movie_player.h b/include/d/actor/d_a_movie_player.h index 0ddc675c24..0e666ae470 100644 --- a/include/d/actor/d_a_movie_player.h +++ b/include/d/actor/d_a_movie_player.h @@ -94,6 +94,12 @@ static void __THPAudioInitialize(THPAudioDecodeInfo* info, u8* ptr); #define THP_TEXTURE_SET_COUNT 3 #endif +#if TARGET_PC +namespace dusk { + void MoviePlayerShutdown(); +} +#endif + struct daMP_THPPlayer { /* 0x000 */ DVDFileInfo fileInfo; /* 0x03C */ THPHeader header; diff --git a/include/d/actor/d_a_obj_sw.h b/include/d/actor/d_a_obj_sw.h index 64674b0b9c..9e2793755a 100644 --- a/include/d/actor/d_a_obj_sw.h +++ b/include/d/actor/d_a_obj_sw.h @@ -68,10 +68,8 @@ public: /* 0x904 */ cXyz field_0x904[2]; /* 0x91C */ int field_0x91c; /* 0x920 */ cXyz field_0x920[63]; - /* 0xC14 */ f32 field_0xc14[4]; - /* 0xC24 */ u8 field_0xc24[0xd10 - 0xc24]; - /* 0xD10 */ s8 field_0xd10[4]; - /* 0xD14 */ u8 field_0xd14[0xd50 - 0xd14]; + /* 0xC14 */ f32 field_0xc14[63]; + /* 0xD10 */ s8 field_0xd10[64]; /* 0xD50 */ mDoExt_3DlineMat1_c field_0xd50; /* 0xD8C */ int field_0xd8c; }; diff --git a/include/d/d_com_inf_game.h b/include/d/d_com_inf_game.h index d22d08cd10..964cc34c0f 100644 --- a/include/d/d_com_inf_game.h +++ b/include/d/d_com_inf_game.h @@ -4834,8 +4834,7 @@ inline void dComIfGd_drawXluListDark() { inline void dComIfGd_drawXluListInvisible() { ZoneScoped; #ifdef TARGET_PC - if (dusk::getSettings().game.enableWaterRefraction && - !dusk::getSettings().game.enableFrameInterpolation) { + if (!dusk::getSettings().game.disableWaterRefraction) { #endif g_dComIfG_gameInfo.drawlist.drawXluListInvisible(); #ifdef TARGET_PC @@ -4846,8 +4845,7 @@ inline void dComIfGd_drawXluListInvisible() { inline void dComIfGd_drawOpaListInvisible() { ZoneScoped; #ifdef TARGET_PC - if (dusk::getSettings().game.enableWaterRefraction && - !dusk::getSettings().game.enableFrameInterpolation) { + if (!dusk::getSettings().game.disableWaterRefraction) { #endif g_dComIfG_gameInfo.drawlist.drawOpaListInvisible(); #ifdef TARGET_PC diff --git a/include/d/d_drawlist.h b/include/d/d_drawlist.h index 4126c715f3..8368c9e92e 100644 --- a/include/d/d_drawlist.h +++ b/include/d/d_drawlist.h @@ -209,6 +209,10 @@ public: /* 0x04 */ TGXTexObj* mpTexObj; /* 0x08 */ Mtx mVolumeMtx; /* 0x38 */ Mtx mMtx; +#if TARGET_PC + const void* mVolumeMtxKey; + const void* mMtxKey; +#endif }; // Size: 0x68 struct cBgD_Vtx_t; diff --git a/include/d/d_file_select.h b/include/d/d_file_select.h index 72f7cb7f42..cf081a3b10 100644 --- a/include/d/d_file_select.h +++ b/include/d/d_file_select.h @@ -10,6 +10,7 @@ #include "JSystem/J3DGraphLoader/J3DAnmLoader.h" class dFile_info_c; +class J2DPicture; class dDlst_FileSel_c : public dDlst_base_c { public: @@ -113,6 +114,14 @@ public: /* 0x04 */ J2DScreen* Scr3m; }; +class dDlst_FileSelFade_c : public dDlst_base_c { +public: + void draw(); + virtual ~dDlst_FileSelFade_c() {} + + /* 0x04 */ J2DPicture* mpPict; +}; + class dFs_HIO_c : public JORReflexible { public: dFs_HIO_c(); @@ -676,6 +685,9 @@ public: #if PLATFORM_GCN /* 0x2378 */ J2DPicture* mpFadePict; #endif +#ifdef TARGET_PC + dDlst_FileSelFade_c mFadeDlst; +#endif #if PLATFORM_WII || PLATFORM_SHIELD /* 0x2376 */ u8 field_0x2376[SAVEFILE_SIZE]; @@ -684,6 +696,10 @@ public: #endif }; +#ifdef TARGET_PC +STATIC_ASSERT(sizeof(dFile_select_c) == 0x237C + sizeof(dDlst_FileSelFade_c)); +#else STATIC_ASSERT(sizeof(dFile_select_c) == 0x237C); +#endif #endif /* D_FILE_D_FILE_SELECT_H */ diff --git a/include/d/d_menu_fmap.h b/include/d/d_menu_fmap.h index 026e98e056..48db37fef3 100644 --- a/include/d/d_menu_fmap.h +++ b/include/d/d_menu_fmap.h @@ -75,7 +75,9 @@ public: /* 0x8 */ BE(u16) mAreaName; /* 0xA */ u8 mCount; #ifdef _MSVC_LANG - u8* __get_mRoomNos() const { return (u8*)(this + 1); } + // Room numbers start at offset 0xB (right after mCount), NOT at sizeof(data)=12. + // (u8*)(this+1) would give offset 12 because MSVC sizeof=12; use &mCount+1 instead. + u8* __get_mRoomNos() const { return (u8*)&mCount + 1; } __declspec(property(get = __get_mRoomNos)) u8* mRoomNos; #else /* 0xB */ u8 mRoomNos[0]; diff --git a/include/d/d_menu_ring.h b/include/d/d_menu_ring.h index f28c20aac2..39d29a35e8 100644 --- a/include/d/d_menu_ring.h +++ b/include/d/d_menu_ring.h @@ -204,6 +204,9 @@ private: /* 0x6D1 */ u8 field_0x6d1; /* 0x6D2 */ u8 field_0x6d2; /* 0x6D3 */ u8 field_0x6d3; +#if TARGET_PC + f32 mSelectItemSlideElapsed[4]; +#endif }; #endif /* D_MENU_D_MENU_RING_H */ diff --git a/include/d/d_select_cursor.h b/include/d/d_select_cursor.h index c77da550f8..1bd5991d16 100644 --- a/include/d/d_select_cursor.h +++ b/include/d/d_select_cursor.h @@ -47,6 +47,10 @@ public: mPositionY = y; } +#ifdef TARGET_PC + void refreshAspectScale(); +#endif + void onUpdateFlag() { mUpdateFlag = true; } void resetUpdateFlag() { mUpdateFlag = false; } @@ -79,6 +83,9 @@ private: /* 0x58 */ f32 mPositionX; /* 0x5C */ f32 mPositionY; /* 0x60 */ f32 mParam1; +#ifdef TARGET_PC + f32 mBaseParam1; +#endif /* 0x64 */ f32 mParam2; /* 0x68 */ f32 mParam3; /* 0x6C */ f32 mParam4; diff --git a/include/dusk/audio/DuskAudioSystem.h b/include/dusk/audio/DuskAudioSystem.h index 724776f5f1..8cdd757c82 100644 --- a/include/dusk/audio/DuskAudioSystem.h +++ b/include/dusk/audio/DuskAudioSystem.h @@ -12,6 +12,8 @@ namespace dusk::audio { void SetMasterVolume(f32 value); + void SetPaused(bool paused); + u32 GetResetCount(int channelIdx); f32 VolumeFromU16(u16 value); diff --git a/include/dusk/discord_presence.hpp b/include/dusk/discord_presence.hpp new file mode 100644 index 0000000000..899b2ad5f9 --- /dev/null +++ b/include/dusk/discord_presence.hpp @@ -0,0 +1,18 @@ +#pragma once + +#ifdef DUSK_DISCORD_RPC + +namespace dusk { +namespace discord { + +void Initialize(); + +void RunCallbacks(); + +void UpdatePresence(); + +void Shutdown(); +} +} + +#endif // DUSK_DISCORD_RPC diff --git a/include/dusk/dusk.h b/include/dusk/dusk.h index b751990d9c..911ddbb535 100644 --- a/include/dusk/dusk.h +++ b/include/dusk/dusk.h @@ -6,7 +6,6 @@ #include "aurora/gfx.h" extern AuroraInfo auroraInfo; -extern const char* configPath; namespace dusk { extern AuroraStats lastFrameAuroraStats; diff --git a/include/dusk/frame_interpolation.h b/include/dusk/frame_interpolation.h index 3aadfe8520..8c19e7e59b 100644 --- a/include/dusk/frame_interpolation.h +++ b/include/dusk/frame_interpolation.h @@ -1,13 +1,12 @@ -#ifndef DUSK_FRAME_INTERP_H -#define DUSK_FRAME_INTERP_H +#pragma once #include #include #include #include -struct cXyz; class camera_process_class; +class view_class; #ifdef __cplusplus namespace dusk { @@ -15,36 +14,39 @@ namespace frame_interp { void ensure_initialized(); -void begin_record_camera(); void begin_record(); void end_record(); -void interpolate(float step); +void begin_sim_tick(); +void begin_frame(bool enabled, bool is_sim_frame, float step); +void interpolate(); float get_interpolation_step(); -void notify_presentation_frame(); void request_presentation_sync(); bool presentation_sync_active(); -void notify_sim_tick_complete(); -uint32_t begin_presentation_ui_pass(); -uint32_t get_presentation_ui_advance_ticks(); -void end_presentation_ui_pass(); +bool is_enabled(); + +// TODO: These should be phased out as UI is progressively updated to use game_clock +void set_ui_tick_pending(bool value); +bool get_ui_tick_pending(); + +bool is_sim_frame(); -void open_child(const void* key, int32_t id); -void close_child(); void record_camera(::camera_process_class* cam, int camera_id); -void record_final_mtx_raw(const Mtx* dest, const Mtx src); -void record_final_mtx_raw_tagged(const Mtx* dest, const Mtx src, uint64_t stable_tag); +void interp_view(::view_class* view); +void record_final_mtx(Mtx m, const void *key); +void record_final_mtx(Mtx m); -bool lookup_replacement(const void* source, Mtx out); +bool lookup_replacement(const void* key, Mtx out); bool lookup_concat_replacement(const void* lhs, const void* rhs, Mtx out); -void camera_eye_from_view_mtx(MtxP view_mtx, cXyz* o_eye); -bool build_star_view(Mtx o_view, Mtx o_cam_billboard_base, cXyz* o_anchor_eye, float* o_fovy); +typedef void (*InterpolationCallBack)(bool isSimFrame, void* pUserWork); +// call on a sim tick, will get called during presentation +void add_interpolation_callback(InterpolationCallBack pCallBack, void* pUserWork); + +void begin_presentation_camera(); +void end_presentation_camera(); -uint64_t alloc_simple_shadow_pair_base(); } // namespace frame_interp } // namespace dusk #endif - -#endif diff --git a/include/dusk/game_clock.h b/include/dusk/game_clock.h new file mode 100644 index 0000000000..8bb277e070 --- /dev/null +++ b/include/dusk/game_clock.h @@ -0,0 +1,26 @@ +#pragma once + +namespace dusk::game_clock { + +void ensure_initialized(); +void reset_frame_timer(); + +constexpr float sim_pace() { return 1.0f / 30.0f; } +constexpr float period_for_original_frames(float frame_count) { return frame_count * sim_pace(); } +constexpr float ui_maximum_dt() { return 0.05f; } +constexpr float ui_initial_dt() { return 1.0f / 60.0f; } + +struct MainLoopPacer { + float presentation_dt_seconds; + bool is_interpolating; + int sim_ticks_to_run; + float sim_pace; +}; + +MainLoopPacer advance_main_loop(); +void commit_sim_tick(); +float sample_interpolation_step(); + +float consume_interval(const void* consumer); + +} // namespace dusk::game_clock diff --git a/include/dusk/gyro.h b/include/dusk/gyro.h new file mode 100644 index 0000000000..279aeae16e --- /dev/null +++ b/include/dusk/gyro.h @@ -0,0 +1,17 @@ +#ifndef DUSK_GYRO_H +#define DUSK_GYRO_H + +namespace dusk::gyro { +void read(float dt); +void getAimDeltas(float& out_yaw, float& out_pitch); +bool queryGyroAimContext(); + +void rollgoalTick(bool play_active, s16 camera_yaw); +void rollgoalTableOffset(s16& out_ax, s16& out_az); + +extern bool s_sensor_keep_alive; +bool get_sensor_keep_alive(); +void set_sensor_keep_alive(bool value); +} // namespace dusk::gyro + +#endif diff --git a/include/dusk/gyro_aim.h b/include/dusk/gyro_aim.h deleted file mode 100644 index aafc926811..0000000000 --- a/include/dusk/gyro_aim.h +++ /dev/null @@ -1,10 +0,0 @@ -#ifndef DUSK_GYRO_AIM_H -#define DUSK_GYRO_AIM_H - -namespace dusk::gyro_aim { -void read(float dt, bool context_active); -void consumeAimDeltas(float& out_yaw_rad, float& out_pitch_rad); -bool queryGyroAimItemContext(); -} // namespace dusk::gyro_aim - -#endif diff --git a/include/dusk/logging.h b/include/dusk/logging.h index 0a9cbf238d..9b31b96bf2 100644 --- a/include/dusk/logging.h +++ b/include/dusk/logging.h @@ -4,10 +4,12 @@ #include #include +#include + void aurora_log_callback(AuroraLogLevel level, const char* module, const char* message, unsigned int len); namespace dusk { - void InitializeFileLogging(const char* configDir, AuroraLogLevel logLevel); + void InitializeFileLogging(const std::filesystem::path& configDir, AuroraLogLevel logLevel); void ShutdownFileLogging(); const char* GetLogFilePath(); void SendToStubLog(AuroraLogLevel level, const char* module, const char* message); diff --git a/include/dusk/main.h b/include/dusk/main.h index a9194e2aba..065f507d36 100644 --- a/include/dusk/main.h +++ b/include/dusk/main.h @@ -1,10 +1,14 @@ #ifndef DUSK_MAIN_H #define DUSK_MAIN_H +#include + namespace dusk { extern bool IsRunning; extern bool IsShuttingDown; extern bool IsGameLaunched; + extern bool IsFocusPaused; + extern std::filesystem::path ConfigPath; } #endif // DUSK_MAIN_H diff --git a/include/dusk/scope_guard.hpp b/include/dusk/scope_guard.hpp new file mode 100644 index 0000000000..281d23434d --- /dev/null +++ b/include/dusk/scope_guard.hpp @@ -0,0 +1,20 @@ +#ifndef DUSK_SCOPE_GUARD_HPP +#define DUSK_SCOPE_GUARD_HPP + +#include + +class SimpleScopeGuard { +public: + // Store the function in the constructor + explicit SimpleScopeGuard(const std::function& func) : m_func(func) {} + + // Run the function when the object goes out of scope + ~SimpleScopeGuard() { + if (m_func) m_func(); + } + +private: + std::function m_func; +}; + +#endif //DUSK_SCOPE_GUARD_HPP diff --git a/include/dusk/settings.h b/include/dusk/settings.h index 67dce3c067..2cec252006 100644 --- a/include/dusk/settings.h +++ b/include/dusk/settings.h @@ -61,19 +61,23 @@ struct UserSettings { ConfigVar noMissClimbing; ConfigVar fastTears; ConfigVar instantSaves; + ConfigVar instantText; ConfigVar sunsSong; // Preferences ConfigVar enableMirrorMode; ConfigVar invertCameraXAxis; ConfigVar disableMainHUD; + ConfigVar pauseOnFocusLost; // Graphics ConfigVar bloomMode; ConfigVar bloomMultiplier; - ConfigVar enableWaterRefraction; + ConfigVar disableWaterRefraction; ConfigVar enableFrameInterpolation; + ConfigVar internalResolutionScale; ConfigVar shadowResolutionMultiplier; + ConfigVar enableDepthOfField; // Audio ConfigVar noLowHpSound; @@ -81,12 +85,25 @@ struct UserSettings { // Input ConfigVar enableGyroAim; - ConfigVar gyroAimSensitivityX; - ConfigVar gyroAimSensitivityY; - ConfigVar gyroAimInvertPitch; - ConfigVar gyroAimInvertYaw; + ConfigVar enableGyroRollgoal; + ConfigVar gyroSensitivityX; + ConfigVar gyroSensitivityY; + ConfigVar gyroSensitivityRollgoal; + ConfigVar gyroSmoothing; + ConfigVar gyroDeadband; + ConfigVar gyroInvertPitch; + ConfigVar gyroInvertYaw; // Cheats + ConfigVar infiniteHearts; + ConfigVar infiniteArrows; + ConfigVar infiniteBombs; + ConfigVar infiniteOil; + ConfigVar infiniteOxygen; + ConfigVar infiniteRupees; + ConfigVar moonJump; + ConfigVar superClawshot; + ConfigVar alwaysGreatspin; ConfigVar enableFastIronBoots; ConfigVar canTransformAnywhere; ConfigVar fastSpinner; @@ -106,6 +123,7 @@ struct UserSettings { ConfigVar showPipelineCompilation; ConfigVar wasPresetChosen; ConfigVar enableCrashReporting; + ConfigVar duskMenuOpen; } backend; }; diff --git a/include/f_pc/f_pc_leaf.h b/include/f_pc/f_pc_leaf.h index 33d7e85d29..414d706b6a 100644 --- a/include/f_pc/f_pc_leaf.h +++ b/include/f_pc/f_pc_leaf.h @@ -25,7 +25,7 @@ typedef struct leafdraw_class : base_process_class { #endif /* 0xB8 */ leafdraw_method_class* leaf_methods; /* 0xBC */ s8 unk_0xBC; - /* 0xBD */ u8 unk_0xBD; + /* 0xBD */ u8 draw_interp_frame; /* 0xBE */ draw_priority_class draw_priority; } leafdraw_class; diff --git a/include/f_pc/f_pc_node.h b/include/f_pc/f_pc_node.h index 3be7fe6c8f..0d96fd2de5 100644 --- a/include/f_pc/f_pc_node.h +++ b/include/f_pc/f_pc_node.h @@ -18,6 +18,7 @@ typedef struct process_node_class { /* 0x0BC */ layer_class layer; /* 0x0E8 */ node_list_class layer_nodelist[16]; /* 0x1A8 */ s8 unk_0x1A8; + /* 0x1A9 */ s8 draw_interp_frame; } process_node_class; typedef struct node_process_profile_definition { diff --git a/include/m_Do/m_Do_graphic.h b/include/m_Do/m_Do_graphic.h index c6b22654fa..5d29049520 100644 --- a/include/m_Do/m_Do_graphic.h +++ b/include/m_Do/m_Do_graphic.h @@ -125,8 +125,15 @@ public: #if TARGET_PC static f32 hudAspectScaleDown; static f32 hudAspectScaleUp; - static f32 ScaleHUDXLeft(f32 baseX) { return getMinXF() + baseX; } - static f32 ScaleHUDXRight(f32 baseX) { return -getMinXF() + baseX; } + static void updateSafeAreaBounds(); + static f32 getSafeMinXF() { return m_safeMinXF; } + static f32 getSafeMinYF() { return m_safeMinYF; } + static f32 getSafeWidthF() { return m_safeWidthF; } + static f32 getSafeHeightF() { return m_safeHeightF; } + static f32 getSafeMaxXF() { return m_safeMaxXF; } + static f32 getSafeMaxYF() { return m_safeMaxYF; } + static f32 ScaleHUDXLeft(f32 baseX) { return getSafeMinXF() + baseX; } + static f32 ScaleHUDXRight(f32 baseX) { return getSafeMaxXF() - FB_WIDTH_BASE + baseX; } #endif static void setBlureMtx(const Mtx m) { @@ -279,12 +286,7 @@ public: #if WIDESCREEN_SUPPORT static void setTvSize(); - #if TARGET_PC - static void onWide(f32 width, f32 height); - #else static void onWide(); - #endif - static void offWide(); static u8 isWide(); @@ -297,7 +299,7 @@ public: #endif #if TARGET_PC - static void setWindowSize(AuroraWindowSize const& size); + static void updateRenderSize(); #endif static TGXTexObj mFrameBufferTexObj; @@ -369,6 +371,15 @@ public: static int m_height; static f32 m_heightF; static f32 m_widthF; + + #if TARGET_PC + static f32 m_safeMinXF; + static f32 m_safeMinYF; + static f32 m_safeMaxXF; + static f32 m_safeMaxYF; + static f32 m_safeWidthF; + static f32 m_safeHeightF; + #endif #endif }; diff --git a/include/m_Do/m_Do_lib.h b/include/m_Do/m_Do_lib.h index 8130cab8da..14e57ddf5a 100644 --- a/include/m_Do/m_Do_lib.h +++ b/include/m_Do/m_Do_lib.h @@ -43,10 +43,6 @@ struct mDoLib_clipper { }; void mDoLib_project(Vec* src, Vec* dst); -#if TARGET_PC -void mDoLib_project(Vec* src, Vec* dst, JGeometry::TBox2 viewport); -#endif - u32 mDoLib_setResTimgObj(ResTIMG const* res, TGXTexObj* o_texObj, u32 tlut_name, GXTlutObj* o_tlutObj); void mDoLib_pos2camera(Vec* src, Vec* dst); diff --git a/libs/JSystem/include/JSystem/J3DGraphAnimator/J3DModel.h b/libs/JSystem/include/JSystem/J3DGraphAnimator/J3DModel.h index a27db4fa00..2e5b3bd38d 100644 --- a/libs/JSystem/include/JSystem/J3DGraphAnimator/J3DModel.h +++ b/libs/JSystem/include/JSystem/J3DGraphAnimator/J3DModel.h @@ -79,6 +79,10 @@ public: virtual void viewCalc(); virtual ~J3DModel() {} +#if TARGET_PC + static void interp_callback(bool isSimFrame, void* pUserWork); +#endif + J3DModelData* getModelData() { return mModelData; } void onFlag(u32 flag) { mFlags |= flag; } @@ -105,9 +109,7 @@ public: void setAnmMtx(int jointNo, Mtx m) { mMtxBuffer->setAnmMtx(jointNo, m); #ifdef TARGET_PC - dusk::frame_interp::record_final_mtx_raw( - reinterpret_cast(mMtxBuffer->getAnmMtx(jointNo)), - mMtxBuffer->getAnmMtx(jointNo)); + dusk::frame_interp::record_final_mtx(mMtxBuffer->getAnmMtx(jointNo)); #endif } MtxP getAnmMtx(int jointNo) { return mMtxBuffer->getAnmMtx(jointNo); } diff --git a/libs/JSystem/include/JSystem/J3DGraphAnimator/J3DModelData.h b/libs/JSystem/include/JSystem/J3DGraphAnimator/J3DModelData.h index 6e3d54ff45..52e5018c1b 100644 --- a/libs/JSystem/include/JSystem/J3DGraphAnimator/J3DModelData.h +++ b/libs/JSystem/include/JSystem/J3DGraphAnimator/J3DModelData.h @@ -23,6 +23,10 @@ public: void syncJ3DSysPointers() const; void syncJ3DSysFlags() const; +#if TARGET_PC + bool needsInterpCallBack() const; +#endif + virtual ~J3DModelData() {} void simpleCalcMaterial(Mtx mtx) { simpleCalcMaterial(0, mtx); } diff --git a/libs/JSystem/include/JSystem/J3DGraphBase/J3DMaterial.h b/libs/JSystem/include/JSystem/J3DGraphBase/J3DMaterial.h index 829156ad1b..70e368eec4 100644 --- a/libs/JSystem/include/JSystem/J3DGraphBase/J3DMaterial.h +++ b/libs/JSystem/include/JSystem/J3DGraphBase/J3DMaterial.h @@ -33,6 +33,9 @@ public: void copy(J3DMaterial*); s32 newSharedDisplayList(u32); s32 newSingleSharedDisplayList(u32); +#if TARGET_PC + bool needsInterpCallBack() const; +#endif virtual void calc(f32 const (*)[4]); virtual void calcDiffTexMtx(f32 const (*)[4]); @@ -46,7 +49,6 @@ public: virtual void change(); J3DMaterial() { initialize(); } - ~J3DMaterial() {} J3DMaterial* getNext() { return mNext; } J3DShape* getShape() { return mShape; } J3DTevBlock* getTevBlock() { return mTevBlock; } diff --git a/libs/JSystem/include/JSystem/JFramework/JFWDisplay.h b/libs/JSystem/include/JSystem/JFramework/JFWDisplay.h index fab39a296b..28967cc0a2 100644 --- a/libs/JSystem/include/JSystem/JFramework/JFWDisplay.h +++ b/libs/JSystem/include/JSystem/JFramework/JFWDisplay.h @@ -101,10 +101,6 @@ public: void setDrawDoneMethod(EDrawDone drawDone) { mDrawDoneMethod = drawDone; } void setFader(JUTFader* fader) { mFader = fader; } -#ifdef TARGET_PC - // For frame interpolation - void setFaderSimSteps(u32 steps); -#endif void resetFader() { setFader(NULL); } JUTFader* getFader() const { return mFader; } void setClearColor(JUtility::TColor color) { mClearColor = color; } diff --git a/libs/JSystem/include/JSystem/JUtility/JUTFader.h b/libs/JSystem/include/JSystem/JUtility/JUTFader.h index 6c98a6840f..b85e4a6cd4 100644 --- a/libs/JSystem/include/JSystem/JUtility/JUTFader.h +++ b/libs/JSystem/include/JSystem/JUtility/JUTFader.h @@ -11,8 +11,10 @@ class JUTFader { public: enum EStatus { - UNKSTATUS_M1 = -1, - UNKSTATUS_0 = 0, + None, + Wait, + FadeIn, + FadeOut, }; JUTFader(int, int, int, int, JUtility::TColor); @@ -29,12 +31,12 @@ public: void setColor(JUtility::TColor color) { mColor.set(color); } /* 0x04 */ s32 mStatus; - /* 0x08 */ u16 field_0x8; - /* 0x0A */ u16 field_0xa; + /* 0x08 */ u16 mDuration; + /* 0x0A */ u16 mTimer; /* 0x0C */ JUtility::TColor mColor; /* 0x10 */ JGeometry::TBox2 mBox; - /* 0x20 */ int mEStatus; - /* 0x24 */ u32 field_0x24; + /* 0x20 */ int mStatusTimer; + /* 0x24 */ u32 mNextStatus; }; #endif /* JUTFADER_H */ diff --git a/libs/JSystem/include/JSystem/JUtility/JUTVideo.h b/libs/JSystem/include/JSystem/JUtility/JUTVideo.h index 9a23a4dc7e..dfc6fd0b6c 100644 --- a/libs/JSystem/include/JSystem/JUtility/JUTVideo.h +++ b/libs/JSystem/include/JSystem/JUtility/JUTVideo.h @@ -33,24 +33,16 @@ public: static void postRetraceProc(u32); static void drawDoneCallback(); - u16 getFbWidth() const { - #if TARGET_PC - return m_WindowSize.fb_width; - #else - return mRenderObj->fbWidth; - #endif - } - u16 getEfbHeight() const { - #if TARGET_PC - return m_WindowSize.fb_height; - #else - return mRenderObj->efbHeight; - #endif - } + u16 getFbWidth() const { return mRenderObj->fbWidth; } + u16 getEfbHeight() const { return mRenderObj->efbHeight; } void getBounds(u16& width, u16& height) const { width = (u16)getFbWidth(); height = (u16)getEfbHeight(); } +#ifdef TARGET_PC + u32 getRenderWidth() const { return mRenderWidth; } + u32 getRenderHeight() const { return mRenderHeight; } +#endif u16 getXfbHeight() const { return u16(mRenderObj->xfbHeight); } u8 isAntiAliasing() const { return u8(mRenderObj->aa); } Pattern getSamplePattern() const { return mRenderObj->sample_pattern; } @@ -63,7 +55,7 @@ public: GXRenderModeObj* getRenderMode() const { return mRenderObj; } #if TARGET_PC - void setWindowSize(AuroraWindowSize const& size); + void setRenderSize(u32 width, u32 height); #endif private: @@ -89,7 +81,8 @@ private: #if TARGET_PC public: - AuroraWindowSize m_WindowSize; + u32 mRenderWidth; + u32 mRenderHeight; #endif }; diff --git a/libs/JSystem/src/J2DGraph/J2DGrafContext.cpp b/libs/JSystem/src/J2DGraph/J2DGrafContext.cpp index 36f54a23c6..3ac3e5d197 100644 --- a/libs/JSystem/src/J2DGraph/J2DGrafContext.cpp +++ b/libs/JSystem/src/J2DGraph/J2DGrafContext.cpp @@ -64,10 +64,6 @@ void J2DGrafContext::setup2D() { } void J2DGrafContext::setScissor() { -#if TARGET_PC - GXSetScissor(mScissorBounds.i.x, mScissorBounds.i.y, mScissorBounds.getWidth(), - mScissorBounds.getHeight()); -#else JGeometry::TBox2 bounds(0, 0, 1024, 1024); JGeometry::TBox2 curBounds(mScissorBounds); mScissorBounds.intersect(bounds); @@ -81,7 +77,6 @@ void J2DGrafContext::setScissor() { } else { GXSetScissor(0, 0, 0, 0); } -#endif } void J2DGrafContext::scissor(JGeometry::TBox2 const& bounds) { diff --git a/libs/JSystem/src/J3DGraphAnimator/J3DModel.cpp b/libs/JSystem/src/J3DGraphAnimator/J3DModel.cpp index f2bd737b4f..8c895d5077 100644 --- a/libs/JSystem/src/J3DGraphAnimator/J3DModel.cpp +++ b/libs/JSystem/src/J3DGraphAnimator/J3DModel.cpp @@ -97,6 +97,16 @@ s32 J3DModel::entryModelData(J3DModelData* pModelData, u32 mdlFlags, u32 mtxNum) return kJ3DError_Success; } +#if TARGET_PC +void J3DModel::interp_callback(bool isSimFrame, void* pUserWork) { + J3DModel* i_this = static_cast(pUserWork); + if (!isSimFrame) { + i_this->calcMaterial(); + i_this->diff(); + } +} +#endif + s32 J3DModel::createShapePacket(J3DModelData* pModelData) { J3D_ASSERTMSG(173, pModelData != NULL, "Error : null pointer."); @@ -452,11 +462,11 @@ void J3DModel::calc() { #ifdef TARGET_PC for (u16 i = 0; i < mModelData->getJointNum(); ++i) { - dusk::frame_interp::record_final_mtx_raw(reinterpret_cast(getAnmMtx(i)), getAnmMtx(i)); + dusk::frame_interp::record_final_mtx(getAnmMtx(i)); } for (u16 i = 0; i < mModelData->getWEvlpMtxNum(); ++i) { - dusk::frame_interp::record_final_mtx_raw(reinterpret_cast(getWeightAnmMtx(i)), getWeightAnmMtx(i)); + dusk::frame_interp::record_final_mtx(getWeightAnmMtx(i)); } #endif } @@ -485,6 +495,11 @@ void J3DModel::entry() { joint->entryIn(); } } + +#if TARGET_PC + if (mModelData->needsInterpCallBack()) + dusk::frame_interp::add_interpolation_callback(&J3DModel::interp_callback, this); +#endif } void J3DModel::viewCalc() { @@ -496,7 +511,7 @@ void J3DModel::viewCalc() { J3DCalcViewBaseMtx(j3dSys.getViewMtx(), mBaseScale, mBaseTransformMtx, (MtxP)&mInternalView); #ifdef TARGET_PC - dusk::frame_interp::record_final_mtx_raw(&mInternalView, mInternalView); + dusk::frame_interp::record_final_mtx(mInternalView); #endif } } else if (isCpuSkinningOn()) { @@ -504,7 +519,7 @@ void J3DModel::viewCalc() { J3DCalcViewBaseMtx(j3dSys.getViewMtx(), mBaseScale, mBaseTransformMtx, (MtxP)&mInternalView); #ifdef TARGET_PC - dusk::frame_interp::record_final_mtx_raw(&mInternalView, mInternalView); + dusk::frame_interp::record_final_mtx(mInternalView); #endif } } else if (checkFlag(J3DMdlFlag_SkinPosCpu)) { @@ -528,7 +543,7 @@ void J3DModel::viewCalc() { #ifdef TARGET_PC for (u16 i = 0; i < mModelData->getDrawMtxNum(); ++i) { - dusk::frame_interp::record_final_mtx_raw(&getDrawMtxPtr()[i], getDrawMtxPtr()[i]); + dusk::frame_interp::record_final_mtx(getDrawMtxPtr()[i]); } #endif diff --git a/libs/JSystem/src/J3DGraphAnimator/J3DModelData.cpp b/libs/JSystem/src/J3DGraphAnimator/J3DModelData.cpp index e296676187..3eed051b6c 100644 --- a/libs/JSystem/src/J3DGraphAnimator/J3DModelData.cpp +++ b/libs/JSystem/src/J3DGraphAnimator/J3DModelData.cpp @@ -84,6 +84,15 @@ void J3DModelData::simpleCalcMaterial(u16 idx, Mtx param_1) { } } +#if TARGET_PC +bool J3DModelData::needsInterpCallBack() const { + for (u16 i = 0, n = getMaterialNum(); i < n; i++) + if (getMaterialNodePointer(i)->needsInterpCallBack()) + return true; + return false; +} +#endif + void J3DModelData::syncJ3DSysPointers() const { j3dSys.setTexture(getTexture()); j3dSys.setVtxPos(getVtxPosArray(), getVtxNum()); diff --git a/libs/JSystem/src/J3DGraphBase/J3DMaterial.cpp b/libs/JSystem/src/J3DGraphBase/J3DMaterial.cpp index 0c772305b2..48d4731ed9 100644 --- a/libs/JSystem/src/J3DGraphBase/J3DMaterial.cpp +++ b/libs/JSystem/src/J3DGraphBase/J3DMaterial.cpp @@ -265,7 +265,7 @@ void J3DMaterial::diff(u32 diffFlags) { } void J3DMaterial::calc(f32 const (*param_0)[4]) { - if (j3dSys.checkFlag(0x40000000)) { + if (j3dSys.checkFlag(J3DSysFlag_PostTexMtx)) { mTexGenBlock->calcPostTexMtx(param_0); } else { mTexGenBlock->calc(param_0); @@ -276,7 +276,7 @@ void J3DMaterial::calc(f32 const (*param_0)[4]) { } void J3DMaterial::calcDiffTexMtx(f32 const (*param_0)[4]) { - if (j3dSys.checkFlag(0x40000000)) { + if (j3dSys.checkFlag(J3DSysFlag_PostTexMtx)) { mTexGenBlock->calcPostTexMtxWithoutViewMtx(param_0); } else { mTexGenBlock->calcWithoutViewMtx(param_0); @@ -288,7 +288,7 @@ void J3DMaterial::setCurrentMtx() { } void J3DMaterial::calcCurrentMtx() { - if (!j3dSys.checkFlag(0x40000000)) { + if (!j3dSys.checkFlag(J3DSysFlag_PostTexMtx)) { mCurrentMtx.setCurrentTexMtx( getTexCoord(0)->getTexGenMtx(), getTexCoord(1)->getTexGenMtx(), @@ -371,6 +371,30 @@ s32 J3DMaterial::newSingleSharedDisplayList(u32 dlSize) { return kJ3DError_Success; } +#if TARGET_PC +bool J3DMaterial::needsInterpCallBack() const { + for (int i = 0, n = getTexGenNum(); i < n; i++) { + J3DTexMtx* pTexMtx = mTexGenBlock->getTexMtx(i); + if (pTexMtx != NULL) { + u32 texMtxMode = pTexMtx->getTexMtxInfo().mInfo & 0x3f; + + // uses j3dSys.getViewMtx() + switch (texMtxMode) { + case J3DTexMtxMode_EnvmapBasic: + case J3DTexMtxMode_EnvmapOld: + case J3DTexMtxMode_Envmap: + case J3DTexMtxMode_ProjmapBasic: + case J3DTexMtxMode_Projmap: + case J3DTexMtxMode_ViewProjmap: + case J3DTexMtxMode_ViewProjmapBasic: + return true; + } + } + } + return false; +} +#endif + void J3DPatchedMaterial::initialize() { J3DMaterial::initialize(); } diff --git a/libs/JSystem/src/J3DGraphBase/J3DTevs.cpp b/libs/JSystem/src/J3DGraphBase/J3DTevs.cpp index 09ddc0b405..719976770b 100644 --- a/libs/JSystem/src/J3DGraphBase/J3DTevs.cpp +++ b/libs/JSystem/src/J3DGraphBase/J3DTevs.cpp @@ -37,9 +37,9 @@ void loadTexCoordGens(u32 texGenNum, J3DTexCoord* texCoords) { var_r28 = 61; J3DGDWriteXFCmdHdr(GX_XF_REG_DUALTEX0, texGenNum); - if (j3dSys.checkFlag(0x40000000)) { + if (j3dSys.checkFlag(J3DSysFlag_PostTexMtx)) { for (int i = 0; i < texGenNum; i++) { - if (texCoords[i].getTexGenMtx() != 60) { + if (texCoords[i].getTexGenMtx() != GX_IDENTITY) { var_r28 = i * 3; } else { var_r28 = 61; diff --git a/libs/JSystem/src/JAudio2/JAISeqMgr.cpp b/libs/JSystem/src/JAudio2/JAISeqMgr.cpp index a1f3554815..1d0e42d47e 100644 --- a/libs/JSystem/src/JAudio2/JAISeqMgr.cpp +++ b/libs/JSystem/src/JAudio2/JAISeqMgr.cpp @@ -120,11 +120,19 @@ void JAISeqMgr::mixOut() { } JAISeq* JAISeqMgr::beginStartSeq_() { - JAISeq* seq = JKR_NEW JAISeq(this, field_0x10); +#ifdef TARGET_PC + if (JAISeq::getFreeMemCount() == 0) { + JUT_WARN(273, "%s", "JASPoolAllocObject::::operator new failed .\n"); + return NULL; + } + return JKR_NEW JAISeq(this, field_0x10); +#else + JAISeq* seq = new JAISeq(this, field_0x10); if (seq == NULL) { JUT_WARN(273, "%s", "JASPoolAllocObject::::operator new failed .\n"); } return seq; +#endif } bool JAISeqMgr::endStartSeq_(JAISeq* seq, JAISoundHandle* handle) { diff --git a/libs/JSystem/src/JFramework/JFWDisplay.cpp b/libs/JSystem/src/JFramework/JFWDisplay.cpp index 38dbc7eab1..9ea826feee 100644 --- a/libs/JSystem/src/JFramework/JFWDisplay.cpp +++ b/libs/JSystem/src/JFramework/JFWDisplay.cpp @@ -205,14 +205,6 @@ void JFWDisplay::preGX() { } } -#ifdef TARGET_PC -static s32 s_faderSimSteps = -1; - -void JFWDisplay::setFaderSimSteps(u32 steps) { - s_faderSimSteps = static_cast(steps); -} -#endif - void JFWDisplay::endGX() { s32 bufferNum = JUTXfb::getManager()->getBufferNum(); u16 width = JUTVideo::getManager()->getFbWidth(); @@ -224,17 +216,10 @@ void JFWDisplay::endGX() { if (mFader != NULL) { ortho.setPort(); #ifdef TARGET_PC - u32 advance_count = 1; - if (dusk::getSettings().game.enableFrameInterpolation && s_faderSimSteps >= 0) { - advance_count = static_cast(s_faderSimSteps); - s_faderSimSteps = -1; - } else { - s_faderSimSteps = -1; - } - for (u32 i = 0; i < advance_count; i++) { + if (dusk::frame_interp::get_ui_tick_pending()) { mFader->advance(); } - if (mFader->getStatus() != 1) { + if (mFader->getStatus() != JUTFader::Wait) { mFader->draw(); } #else @@ -394,6 +379,7 @@ static void waitPrecise(Limiter& limiter, Uint64 targetNs) { static void waitForTick(u32 p1, u16 p2) { #if TARGET_PC if (dusk::getSettings().game.enableFrameInterpolation && !dusk::getTransientSettings().skipFrameRateLimit) { + dusk::frameUsagePct = 0.f; return; } if (dusk::getTransientSettings().skipFrameRateLimit) { diff --git a/libs/JSystem/src/JStudio/JStudio_JStage/object-actor.cpp b/libs/JSystem/src/JStudio/JStudio_JStage/object-actor.cpp index dc0c5c56e8..0ee7d53fb7 100644 --- a/libs/JSystem/src/JStudio/JStudio_JStage/object-actor.cpp +++ b/libs/JSystem/src/JStudio/JStudio_JStage/object-actor.cpp @@ -314,10 +314,17 @@ void JStudio_JStage::TAdaptor_actor::getJSG_SRT_(JStudio::TControl const* pContr } void JStudio_JStage::TAdaptor_actor::TVVOutput_ANIMATION_FRAME_::operator()( - f32 param_1, JStudio::TAdaptor* adaptor) const { + f32 param_1, JStudio::TAdaptor* adaptor) const { +#if TARGET_PC + TAdaptor_actor* actor_adaptor = static_cast(adaptor); + JStage::TActor* actor = actor_adaptor->get_pJSG_(); + // field_0x8 is always hardcoded to either 305 or 309 + u32 idx = (field_0x8 == 305) ? actor_adaptor->field_0x130 : actor_adaptor->field_0x134; +#else JStage::TActor* actor = static_cast(adaptor)->get_pJSG_(); // not sure what this bit is u32 idx = *(u32*)(((uintptr_t)adaptor - 1) + field_0x8); +#endif u8 idx_lowBytes = idx; u8 idx_highBytes = idx >> 8; diff --git a/libs/JSystem/src/JUtility/JUTFader.cpp b/libs/JSystem/src/JUtility/JUTFader.cpp index f7fa963e63..599ae22720 100644 --- a/libs/JSystem/src/JUtility/JUTFader.cpp +++ b/libs/JSystem/src/JUtility/JUTFader.cpp @@ -10,51 +10,51 @@ JUTFader::JUTFader(int x, int y, int width, int height, JUtility::TColor pColor) : mColor(pColor), mBox(x, y, x + width, y + height) { - mStatus = 0; - field_0x8 = 0; - field_0xa = 0; - field_0x24 = 0; - mEStatus = UNKSTATUS_M1; + mStatus = None; + mDuration = 0; + mTimer = 0; + mNextStatus = 0; + mStatusTimer = -1; } void JUTFader::advance() { - if (0 <= mEStatus && mEStatus-- == 0) { - mStatus = field_0x24; + if (0 <= mStatusTimer && mStatusTimer-- == 0) { + mStatus = mNextStatus; } - if (mStatus == 1) { + if (mStatus == Wait) { return; } switch (mStatus) { - case 0: + case None: mColor.a = 0xFF; break; - case 2: + case FadeIn: #if AVOID_UB - if (field_0x8 == 0) { - mStatus = 1; + if (mDuration == 0) { + mStatus = Wait; break; } #endif - mColor.a = 0xFF - ((++field_0xa * 0xFF) / field_0x8); + mColor.a = 0xFF - ((++mTimer * 0xFF) / mDuration); - if (field_0xa >= field_0x8) { - mStatus = 1; + if (mTimer >= mDuration) { + mStatus = Wait; } break; - case 3: + case FadeOut: #if AVOID_UB - if (field_0x8 == 0) { - mStatus = 0; + if (mDuration == 0) { + mStatus = None; break; } #endif - mColor.a = ((++field_0xa * 0xFF) / field_0x8); + mColor.a = ((++mTimer * 0xFF) / mDuration); - if (field_0xa >= field_0x8) { - mStatus = 0; + if (mTimer >= mDuration) { + mStatus = None; } break; @@ -77,53 +77,53 @@ void JUTFader::draw() { } } -bool JUTFader::startFadeIn(int param_0) { +bool JUTFader::startFadeIn(int duration) { bool statusCheck = mStatus == 0; if (statusCheck) { - mStatus = 2; - field_0xa = 0; - field_0x8 = param_0; + mStatus = FadeIn; + mTimer = 0; + mDuration = duration; } return statusCheck; } -bool JUTFader::startFadeOut(int param_0) { +bool JUTFader::startFadeOut(int duration) { bool statusCheck = mStatus == 1; if (statusCheck) { - mStatus = 3; - field_0xa = 0; - field_0x8 = param_0; + mStatus = FadeOut; + mTimer = 0; + mDuration = duration; } return statusCheck; } -void JUTFader::setStatus(JUTFader::EStatus i_status, int param_1) { +void JUTFader::setStatus(JUTFader::EStatus i_status, int timer) { switch (i_status) { - case 0: - if (param_1 != 0) { - field_0x24 = 0; - mEStatus = param_1; + case None: + if (timer != 0) { + mNextStatus = None; + mStatusTimer = timer; break; } - mStatus = 0; - field_0x24 = 0; - mEStatus = 0; + mStatus = None; + mNextStatus = None; + mStatusTimer = 0; break; - case 1: - if (param_1 != 0) { - field_0x24 = 1; - mEStatus = param_1; + case Wait: + if (timer != 0) { + mNextStatus = Wait; + mStatusTimer = timer; break; } - mStatus = 1; - field_0x24 = 1; - mEStatus = 0; + mStatus = Wait; + mNextStatus = Wait; + mStatusTimer = 0; break; } } diff --git a/libs/JSystem/src/JUtility/JUTVideo.cpp b/libs/JSystem/src/JUtility/JUTVideo.cpp index f718fcac23..5586342f0e 100644 --- a/libs/JSystem/src/JUtility/JUTVideo.cpp +++ b/libs/JSystem/src/JUtility/JUTVideo.cpp @@ -205,7 +205,8 @@ void JUTVideo::setRenderMode(GXRenderModeObj const* pObj) { void JUTVideo::waitRetraceIfNeed() {} #if TARGET_PC -void JUTVideo::setWindowSize(AuroraWindowSize const& size) { - m_WindowSize = size; +void JUTVideo::setRenderSize(u32 width, u32 height) { + mRenderWidth = width; + mRenderHeight = height; } #endif diff --git a/platforms/android/app/src/main/AndroidManifest.xml b/platforms/android/app/src/main/AndroidManifest.xml index d09b0d2785..e8c2bb29a8 100644 --- a/platforms/android/app/src/main/AndroidManifest.xml +++ b/platforms/android/app/src/main/AndroidManifest.xml @@ -15,7 +15,7 @@ android:allowBackup="true" android:hardwareAccelerated="true" android:appCategory="game" - android:icon="@android:drawable/sym_def_app_icon" + android:icon="@mipmap/icon" android:label="@string/app_name" android:theme="@android:style/Theme.NoTitleBar" android:enableOnBackInvokedCallback="false"> diff --git a/platforms/android/app/src/main/res/mipmap/icon.png b/platforms/android/app/src/main/res/mipmap/icon.png new file mode 100644 index 0000000000..05e11c9b41 Binary files /dev/null and b/platforms/android/app/src/main/res/mipmap/icon.png differ diff --git a/platforms/ios/Assets.car b/platforms/ios/Assets.car new file mode 100644 index 0000000000..5087cfe981 Binary files /dev/null and b/platforms/ios/Assets.car differ diff --git a/platforms/macos/Assets.car b/platforms/macos/Assets.car new file mode 100644 index 0000000000..b835eb03e0 Binary files /dev/null and b/platforms/macos/Assets.car differ diff --git a/platforms/macos/Dusk.icns b/platforms/macos/Dusk.icns new file mode 100644 index 0000000000..c684d8194f Binary files /dev/null and b/platforms/macos/Dusk.icns differ diff --git a/platforms/macos/Info.plist.in b/platforms/macos/Info.plist.in index c91435dc2e..b7bc7b706c 100644 --- a/platforms/macos/Info.plist.in +++ b/platforms/macos/Info.plist.in @@ -13,7 +13,9 @@ CFBundleExecutable ${MACOSX_BUNDLE_EXECUTABLE_NAME} CFBundleIconFile - mainicon.icns + Dusk + CFBundleIconName + Dusk CFBundleIdentifier ${MACOSX_BUNDLE_GUI_IDENTIFIER} CFBundleName diff --git a/res/logo-mascot.webp b/res/logo-mascot.webp new file mode 100644 index 0000000000..c22f3bc90f Binary files /dev/null and b/res/logo-mascot.webp differ diff --git a/src/Z2AudioLib/Z2WolfHowlMgr.cpp b/src/Z2AudioLib/Z2WolfHowlMgr.cpp index cb4f387a21..4866af7a5d 100644 --- a/src/Z2AudioLib/Z2WolfHowlMgr.cpp +++ b/src/Z2AudioLib/Z2WolfHowlMgr.cpp @@ -117,8 +117,8 @@ static Z2WolfHowlLine sNewSong3[9] = { #if TARGET_PC static Z2WolfHowlLine sHowlTimeSong[6] = { - {HOWL_LINE_MID, 20}, {HOWL_LINE_LOW, 20}, {HOWL_LINE_HIGH, 40}, - {HOWL_LINE_MID, 20}, {HOWL_LINE_LOW, 20}, {HOWL_LINE_HIGH, 40}, + {HOWL_LINE_MID, 15}, {HOWL_LINE_LOW, 15}, {HOWL_LINE_HIGH, 30}, + {HOWL_LINE_MID, 15}, {HOWL_LINE_LOW, 15}, {HOWL_LINE_HIGH, 30}, }; #endif @@ -368,9 +368,9 @@ void Z2WolfHowlMgr::setCorrectData(s8 curveID, Z2WolfHowlData* data) { break; #if TARGET_PC case Z2WOLFHOWL_TIMESONG: - cPitchUp = 1.259906f; - cPitchCenter = 0.94387f; - cPitchDown = 0.840885f; + cPitchUp = 1.3348f; + cPitchCenter = 1.0f; + cPitchDown = 0.7937f; break; #endif default: diff --git a/src/d/actor/d_a_alink.cpp b/src/d/actor/d_a_alink.cpp index 6787c53de7..e6943baf96 100644 --- a/src/d/actor/d_a_alink.cpp +++ b/src/d/actor/d_a_alink.cpp @@ -4258,6 +4258,12 @@ int daAlink_c::createHeap() { return 0; } +#if TARGET_PC + // lets try to zero-initialize the arrays instead of having garbage values + std::memset(sp1C, 0, sizeof(J3DTransformInfo) * sp38); + std::memset(sp30, 0, sizeof(Quaternion) * sp38); +#endif + field_0x2060 = JKR_NEW mDoExt_MtxCalcOldFrame(sp1C, sp30); if (field_0x2060 == NULL) { return 0; diff --git a/src/d/actor/d_a_alink_crawl.inc b/src/d/actor/d_a_alink_crawl.inc index 4a657c1de8..2dbf88d3e1 100644 --- a/src/d/actor/d_a_alink_crawl.inc +++ b/src/d/actor/d_a_alink_crawl.inc @@ -37,18 +37,40 @@ void daAlink_c::setCrawlMoveDirectionArrow() { } if (field_0x3198 & 4) { - if (!bvar) { - direction |= data_80452F38; - } else { - direction |= data_80452F39; + #if TARGET_PC + if (dusk::getSettings().game.enableMirrorMode) { + if (!bvar) { + direction |= data_80452F39; + } else { + direction |= data_80452F38; + } + } else + #endif + { + if (!bvar) { + direction |= data_80452F38; + } else { + direction |= data_80452F39; + } } } if (field_0x3198 & 8) { - if (!bvar) { - direction |= data_80452F39; - } else { - direction |= data_80452F38; + #if TARGET_PC + if (dusk::getSettings().game.enableMirrorMode) { + if (!bvar) { + direction |= data_80452F38; + } else { + direction |= data_80452F39; + } + } else + #endif + { + if (!bvar) { + direction |= data_80452F39; + } else { + direction |= data_80452F38; + } } } diff --git a/src/d/actor/d_a_alink_cut.inc b/src/d/actor/d_a_alink_cut.inc index 8e61a262cb..f33d2935c5 100644 --- a/src/d/actor/d_a_alink_cut.inc +++ b/src/d/actor/d_a_alink_cut.inc @@ -817,6 +817,12 @@ BOOL daAlink_c::checkDownAttackState() { } BOOL daAlink_c::checkCutLargeTurnState() const { + #if TARGET_PC + if (dusk::getSettings().game.alwaysGreatspin) { + return TRUE; + } + #endif + return ((dComIfGs_isEventBit(dSv_event_flag_c::F_0344) || checkNoResetFlg3(FLG3_TRANING_CUT_LARGE_TURN)) && dComIfGs_getLife() == dComIfGs_getMaxLifeGauge() ) diff --git a/src/d/actor/d_a_alink_dusk.cpp b/src/d/actor/d_a_alink_dusk.cpp index b35daf445f..85bc12b21c 100644 --- a/src/d/actor/d_a_alink_dusk.cpp +++ b/src/d/actor/d_a_alink_dusk.cpp @@ -257,15 +257,18 @@ void daAlink_c::handleQuickTransform() { procCoMetamorphoseInit(); } -bool daAlink_c::checkGyroAimItemContext() { - if (checkWolf()) { - return false; - } - +bool daAlink_c::checkGyroAimContext() { switch (mProcID) { + case PROC_SUBJECTIVITY: + case PROC_SWIM_SUBJECTIVITY: + case PROC_HORSE_SUBJECTIVITY: + case PROC_CANOE_SUBJECTIVITY: + case PROC_BOARD_SUBJECTIVITY: + case PROC_WOLF_ROPE_SUBJECTIVITY: case PROC_BOW_SUBJECT: case PROC_BOOMERANG_SUBJECT: case PROC_COPY_ROD_SUBJECT: + case PROC_HAWK_SUBJECT: case PROC_HOOKSHOT_SUBJECT: case PROC_SWIM_HOOKSHOT_SUBJECT: case PROC_HORSE_BOW_SUBJECT: diff --git a/src/d/actor/d_a_alink_hook.inc b/src/d/actor/d_a_alink_hook.inc index 3641015993..d152190161 100644 --- a/src/d/actor/d_a_alink_hook.inc +++ b/src/d/actor/d_a_alink_hook.inc @@ -290,6 +290,12 @@ BOOL daAlink_c::checkHookshotStickBG(cBgS_PolyInfo& i_polyinfo) { } #endif + #if TARGET_PC + if (dusk::getSettings().game.superClawshot) { + return TRUE; + } + #endif + if (dComIfG_Bgsp().ChkPolyHSStick(i_polyinfo)) { dBgW_Base* bgw_p = dComIfG_Bgsp().GetBgWBasePointer(i_polyinfo); if (bgw_p != NULL && bgw_p->ChkPushPullOk()) { @@ -448,6 +454,12 @@ void daAlink_c::setHookshotSight() { max_length = mpHIO->mItem.mHookshot.m.mMaxLength; } + #if TARGET_PC + if (dusk::getSettings().game.superClawshot) { + max_length = 69420.0f; + } + #endif + BOOL line_cross = checkSightLine(max_length, &sight_pos); if (mHookTargetAcKeep.getActor() != NULL) { @@ -890,6 +902,14 @@ void daAlink_c::setHookshotPos() { max_length = mpHIO->mItem.mHookshot.m.mMaxLength; } + #if TARGET_PC + if (dusk::getSettings().game.superClawshot) { + return_speed = 2870.0f; + shoot_speed = 2870.0f; + max_length = 69420.0f; + } + #endif + if (mItemMode == HS_MODE_RETURN_e) { if (targetAc_p != NULL) { if (checkLv7BossRoom()) { @@ -899,6 +919,12 @@ void daAlink_c::setHookshotPos() { } } + #if TARGET_PC + if (dusk::getSettings().game.superClawshot) { + return_speed = 500.0f; + } + #endif + if (checkModeFlg(0x400)) { return_speed += current.pos.abs(field_0x3798); } @@ -1548,6 +1574,12 @@ int daAlink_c::procHookshotFly() { f32 temp_f31 = field_0x37d4.abs(); f32 temp_f30 = mpHIO->mItem.mHookshot.m.mStickReturnSpeed + spAC.abs(mHookshotTopPos); + #if TARGET_PC + if (dusk::getSettings().game.superClawshot) { + temp_f30 = 500.0f + spAC.abs(mHookshotTopPos); + } + #endif + if (temp_f31 < temp_f30 || mProcVar1.field_0x300a == 0) { setHookshotReturnEnd(); } else { diff --git a/src/d/actor/d_a_alink_kandelaar.inc b/src/d/actor/d_a_alink_kandelaar.inc index 88c2152e03..339ff90ce6 100644 --- a/src/d/actor/d_a_alink_kandelaar.inc +++ b/src/d/actor/d_a_alink_kandelaar.inc @@ -180,12 +180,7 @@ void daAlink_c::preKandelaarDraw() { mat_p->setTevColor(2, &color); cXyz proj; - - #if TARGET_PC - mDoLib_project(&mKandelaarFlamePos, &proj, {0, 0, FB_WIDTH, FB_HEIGHT}); - #else mDoLib_project(&mKandelaarFlamePos, &proj); - #endif camera_process_class* camera_p = dComIfGp_getCamera(0); f32 trimHeight; diff --git a/src/d/actor/d_a_alink_link.inc b/src/d/actor/d_a_alink_link.inc index 4f8f5fa7b6..b278d866fc 100644 --- a/src/d/actor/d_a_alink_link.inc +++ b/src/d/actor/d_a_alink_link.inc @@ -11,7 +11,7 @@ #include "d/actor/d_a_tag_mhint.h" #if TARGET_PC -#include "dusk/gyro_aim.h" +#include "dusk/gyro.h" #endif bool daAlink_c::checkNoSubjectModeCamera() { @@ -130,7 +130,7 @@ BOOL daAlink_c::setBodyAngleToCamera() { } #if TARGET_PC - if (dusk::getSettings().game.enableGyroAim && checkGyroAimItemContext()) { + if (dusk::getSettings().game.enableGyroAim && checkGyroAimContext()) { f32 gyro_scale = 1.0f; if (checkWolfEyeUp()) { gyro_scale *= 0.6f; @@ -142,17 +142,7 @@ BOOL daAlink_c::setBodyAngleToCamera() { f32 gy_yaw = 0.f; f32 gy_pitch = 0.f; - dusk::gyro_aim::consumeAimDeltas(gy_yaw, gy_pitch); - - if (dusk::getSettings().game.gyroAimInvertPitch) { - gy_pitch = -gy_pitch; - } - if (dusk::getSettings().game.gyroAimInvertYaw) { - gy_yaw = -gy_yaw; - } - if (dusk::getSettings().game.enableMirrorMode) { - gy_yaw = -gy_yaw; - } + dusk::gyro::getAimDeltas(gy_yaw, gy_pitch); shape_angle.y = shape_angle.y + cM_rad2s(gy_yaw * gyro_scale); sp8 = sp8 + cM_rad2s(gy_pitch * gyro_scale); diff --git a/src/d/actor/d_a_balloon_2D.cpp b/src/d/actor/d_a_balloon_2D.cpp index 6d9c66b464..6b5b5a2bd2 100644 --- a/src/d/actor/d_a_balloon_2D.cpp +++ b/src/d/actor/d_a_balloon_2D.cpp @@ -318,11 +318,7 @@ void daBalloon2D_c::addScoreCount(cXyz* param_1, u32 param_2, u8 param_3) { field_0x5f8[current].field_0xf = field_0x5f8[prev].field_0xf; } cXyz acStack_2c; - #if TARGET_PC - mDoLib_project(param_1, &acStack_2c, { 0, 0, FB_WIDTH, FB_HEIGHT }); - #else mDoLib_project(param_1, &acStack_2c); - #endif field_0x5f8[0].field_0x0.set(acStack_2c); field_0x5f8[0].field_0xc = param_2; field_0x5f8[0].field_0xe = 60; diff --git a/src/d/actor/d_a_bg.cpp b/src/d/actor/d_a_bg.cpp index e73723d1f9..9c453b6553 100644 --- a/src/d/actor/d_a_bg.cpp +++ b/src/d/actor/d_a_bg.cpp @@ -623,6 +623,11 @@ int daBg_c::create() { dComIfGp_roomControl_onStatusFlag(roomNo, 0x10); OS_REPORT(" room%d\n", roomNo); + +#if TARGET_PC + draw_interp_frame = true; +#endif + return cPhs_COMPLEATE_e; } diff --git a/src/d/actor/d_a_boomerang.cpp b/src/d/actor/d_a_boomerang.cpp index 64dd71f985..590897640e 100644 --- a/src/d/actor/d_a_boomerang.cpp +++ b/src/d/actor/d_a_boomerang.cpp @@ -337,13 +337,7 @@ void daBoomerang_sight_c::setSight(const cXyz* i_pos, int i_no) { } Vec proj; - - #if TARGET_PC - mDoLib_project(&m_pos[i_no], &proj, {0, 0, FB_WIDTH, FB_HEIGHT}); - #else mDoLib_project(&m_pos[i_no], &proj); - #endif - m_proj_posX[i_no] = proj.x; m_proj_posY[i_no] = proj.y; } diff --git a/src/d/actor/d_a_demo00.cpp b/src/d/actor/d_a_demo00.cpp index 871e9ebca6..99ad6c1587 100644 --- a/src/d/actor/d_a_demo00.cpp +++ b/src/d/actor/d_a_demo00.cpp @@ -1658,12 +1658,7 @@ int daDemo00_c::draw() { MTXCopy(mModel.field_0x5d4->getAnmMtx(0), mDoMtx_stack_c::get()); spb0.set(0.0f, 0.0f, 0.0f); mDoMtx_stack_c::multVec(&spb0, &sp98); - - #if TARGET_PC - mDoLib_project(&sp98, &spa4, {0, 0, FB_WIDTH, FB_HEIGHT}); - #else mDoLib_project(&sp98, &spa4); - #endif if (spa4.x >= -700.0f && spa4.x < 1600.0f && spa4.y >= -200.0f && spa4.y < 600.0f) { if (mModel.mID.field_0x18 == 0 || mModel.mID.field_0x18 == 1) { diff --git a/src/d/actor/d_a_dshutter.cpp b/src/d/actor/d_a_dshutter.cpp index 4a0cf397f4..264f559e07 100644 --- a/src/d/actor/d_a_dshutter.cpp +++ b/src/d/actor/d_a_dshutter.cpp @@ -215,7 +215,15 @@ int daDsh_c::create() { mType = getType(); + // !@bug Static-init only runs once, so slot 0 keeps the first mType's arc name forever. + // GC/Wii dodges this via REL reload; TPHD is statically linked so later gates of a + // different type load the wrong arc and CreateHeap fails. The storage must stay static + // because mResLoader.load holds the pointer past create(), so we just overwrite slot 0 + // each call instead. static const char* l_resName[] = {l_arcName[mType], ""}; +#ifdef TARGET_PC + l_resName[0] = l_arcName[mType]; +#endif int phase = mResLoader.load(l_resName, NULL); if (phase == cPhs_COMPLEATE_e) { diff --git a/src/d/actor/d_a_e_fk.cpp b/src/d/actor/d_a_e_fk.cpp index 87734da262..5840df0495 100644 --- a/src/d/actor/d_a_e_fk.cpp +++ b/src/d/actor/d_a_e_fk.cpp @@ -429,13 +429,7 @@ void daE_FK_c::DamageAction() { bool daE_FK_c::checkViewArea() { Vec proj; - - #if TARGET_PC - mDoLib_project(¤t.pos, &proj, {0, 0, FB_WIDTH, FB_HEIGHT}); - #else mDoLib_project(¤t.pos, &proj); - #endif - return (proj.x >= 0.0f && proj.x <= FB_WIDTH) && (proj.y >= 0.0f && proj.y <= FB_HEIGHT); } diff --git a/src/d/actor/d_a_e_fs.cpp b/src/d/actor/d_a_e_fs.cpp index 54dd10ad81..3ed859c686 100644 --- a/src/d/actor/d_a_e_fs.cpp +++ b/src/d/actor/d_a_e_fs.cpp @@ -463,13 +463,7 @@ static void damage_check(e_fs_class* i_this) { static bool checkViewArea(cXyz* i_pos) { Vec proj; - - #if TARGET_PC - mDoLib_project(i_pos, &proj, {0, 0, FB_WIDTH, FB_HEIGHT}); - #else mDoLib_project(i_pos, &proj); - #endif - bool ret = false; if (proj.x >= 0.0f && proj.x <= FB_WIDTH && proj.y >= 0.0f && proj.y <= FB_HEIGHT) { ret = true; diff --git a/src/d/actor/d_a_e_rd.cpp b/src/d/actor/d_a_e_rd.cpp index 3067cc456a..e540e7dccd 100644 --- a/src/d/actor/d_a_e_rd.cpp +++ b/src/d/actor/d_a_e_rd.cpp @@ -6601,13 +6601,14 @@ static int daE_RD_Execute(e_rd_class* i_this) { 1.2f, }; + #if AVOID_UB + s16 x = 0; + s16 y = 0; + #endif for (int i = 0; i < 2; i++) { MtxPush(); + #if !AVOID_UB s16 x, y; - - #if AVOID_UB - x = 0; - y = 0; #endif if (i == 0) { diff --git a/src/d/actor/d_a_e_sm.cpp b/src/d/actor/d_a_e_sm.cpp index 8894399cec..b85e22e640 100644 --- a/src/d/actor/d_a_e_sm.cpp +++ b/src/d/actor/d_a_e_sm.cpp @@ -1362,13 +1362,7 @@ void daE_SM_c::E_SM_C_Hook() { bool daE_SM_c::CheckViewArea() { Vec vec; - - #if TARGET_PC - mDoLib_project(¤t.pos, &vec, {0, 0, FB_WIDTH, FB_HEIGHT}); - #else mDoLib_project(¤t.pos, &vec); - #endif - bool rv = false; if (vec.x >= 0.0f && vec.x <= FB_WIDTH && vec.y >= 0.0f && vec.y <= FB_HEIGHT) { diff --git a/src/d/actor/d_a_e_wb.cpp b/src/d/actor/d_a_e_wb.cpp index 819439879e..75031c42bb 100644 --- a/src/d/actor/d_a_e_wb.cpp +++ b/src/d/actor/d_a_e_wb.cpp @@ -18,6 +18,8 @@ #include "m_Do/m_Do_controller_pad.h" #include "m_Do/m_Do_graphic.h" #include "res/Object/Always.h" +#include "dusk/dusk.h" +#include "dusk/frame_interpolation.h" #include @@ -184,6 +186,30 @@ static bool hio_set; static daE_WB_HIO_c l_HIO; +#if TARGET_PC +static void e_wb_rein_interp_callback(bool isSimFrame, void* pUserWork) { + e_wb_class* i_this = (e_wb_class*)pUserWork; + if (!i_this->himo_interp_prev_valid || !i_this->himo_interp_curr_valid) { + return; + } + const f32 alpha = dusk::frame_interp::get_interpolation_step(); + for (int r = 0; r < 2; r++) { + cXyz* dst = i_this->himo_mat[r].getPos(0); + for (int i = 0; i < 16; i++) { + const cXyz& p0 = i_this->himo_mat_interp_prev[r][i]; + const cXyz& p1 = i_this->himo_mat_interp_curr[r][i]; + dst[i] = p0 + (p1 - p0) * alpha; + } + } + cXyz* dst = i_this->himo_tex.getPos(0); + for (int i = 0; i < 2; i++) { + const cXyz& p0 = i_this->himo_tex_interp_prev[i]; + const cXyz& p1 = i_this->himo_tex_interp_curr[i]; + dst[i] = p0 + (p1 - p0) * alpha; + } +} +#endif + static void himo_control1(e_wb_class* i_this, cXyz* i_pos, int i_no, s8 param_3) { fopEn_enemy_c* enemy = &i_this->enemy; cXyz mae, ato; @@ -508,6 +534,21 @@ static int daE_WB_Draw(e_wb_class* i_this) { dComIfGd_set3DlineMat(&i_this->himo_mat[1]); i_this->himo_tex.update(2, l_color, &actor->tevStr); dComIfGd_set3DlineMat(&i_this->himo_tex); +#if TARGET_PC + if (dusk::getSettings().game.enableFrameInterpolation) { + if (i_this->himo_interp_curr_valid) { + memcpy(i_this->himo_mat_interp_prev, i_this->himo_mat_interp_curr, sizeof(i_this->himo_mat_interp_curr)); + memcpy(i_this->himo_tex_interp_prev, i_this->himo_tex_interp_curr, sizeof(i_this->himo_tex_interp_curr)); + i_this->himo_interp_prev_valid = true; + } + for (int r = 0; r < 2; r++) { + memcpy(i_this->himo_mat_interp_curr[r], i_this->himo_mat[r].getPos(0), 16 * sizeof(cXyz)); + } + memcpy(i_this->himo_tex_interp_curr, i_this->himo_tex.getPos(0), 2 * sizeof(cXyz)); + i_this->himo_interp_curr_valid = true; + dusk::frame_interp::add_interpolation_callback(&e_wb_rein_interp_callback, i_this); + } +#endif } return 1; @@ -3726,6 +3767,9 @@ static void demo_camera(e_wb_class* i_this) { boss = (e_rdb_class*)fopAcM_SearchByName(fpcNm_E_RDB_e); } cXyz mae, ato, eye, center; +#if TARGET_PC + const s16 entry_demo_mode = i_this->demo_mode; +#endif switch (i_this->demo_mode) { case 1: { @@ -4255,6 +4299,9 @@ static void demo_camera(e_wb_class* i_this) { if (i_this->demo_timer == 325) { fpcM_Search(s_wbZrevise_sub, i_this); +#if TARGET_PC + i_this->demo_cam_sync_ticks = 2; +#endif } if (i_this->demo_timer == 335) { @@ -4495,6 +4542,9 @@ static void demo_camera(e_wb_class* i_this) { i_this->demo_cam_way_spd.z = fabsf(i_this->demo_cam_way.z - i_this->demo_cam_ctr.z); i_this->demo_cam_morf = 0; pla->setPlayerPosAndAngle(&pla->current.pos, pla->shape_angle.y - 4000, 0); +#if TARGET_PC + dusk::frame_interp::request_presentation_sync(); +#endif } if (i_this->demo_timer == 345) { daPy_getPlayerActorClass()->setThrowDamage(boss->enemy.shape_angle.y - 8000 + TREG_S(8), @@ -4741,6 +4791,9 @@ static void demo_camera(e_wb_class* i_this) { i_this->demo_cam_eye.x += 300.0f + VREG_F(8); i_this->demo_cam_eye.y += 150.0f + VREG_F(9); i_this->demo_cam_eye.z -= 1400.0f + VREG_F(10); +#if TARGET_PC + dusk::frame_interp::request_presentation_sync(); +#endif } } else { i_this->demo_cam_eye = enemy->current.pos; @@ -4996,6 +5049,15 @@ static void demo_camera(e_wb_class* i_this) { } } } +#if TARGET_PC + if (entry_demo_mode != i_this->demo_mode) { + i_this->demo_cam_sync_ticks = 2; + } + if (i_this->demo_cam_sync_ticks > 0) { + dusk::frame_interp::request_presentation_sync(); + i_this->demo_cam_sync_ticks--; + } +#endif } static void anm_se_eff_set(e_wb_class* i_this) { diff --git a/src/d/actor/d_a_mant.cpp b/src/d/actor/d_a_mant.cpp index bcd7f05986..282502419f 100644 --- a/src/d/actor/d_a_mant.cpp +++ b/src/d/actor/d_a_mant.cpp @@ -12,6 +12,7 @@ #if TARGET_PC #include "dusk/dvd_asset.hpp" +#include "dusk/frame_interpolation.h" static u8* l_Egnd_mantTEX_get() { alignas(32) static u8 buf[0x4000]; static bool _ = (dusk::LoadRelAsset(buf, "/rel/Final/Release/d_a_mant.rel", 0x1C00, 0x4000), true); return buf; } static u8* l_Egnd_mantTEX_U_get() { alignas(32) static u8 buf[0x4000]; static bool _ = (dusk::LoadRelAsset(buf, "/rel/Final/Release/d_a_mant.rel", 0x5C00, 0x4000), true); return buf; } static u8* l_Egnd_mantPAL_get() { alignas(32) static u8 buf[0x60]; static bool _ = (dusk::LoadRelAsset(buf, "/rel/Final/Release/d_a_mant.rel", 0x9C00, 0x60), true); return buf; } @@ -267,6 +268,37 @@ static void* tex_d[2] = { static char lbl_277_bss_0; +#if TARGET_PC +static void mant_build_anchor_frame(const cXyz& anchor_a, const cXyz& anchor_b, Mtx out) { + cXyz axis_x = anchor_b - anchor_a; + if (!axis_x.normalizeRS()) { + axis_x = cXyz::BaseX; + } + + cXyz helper = fabsf(axis_x.y) > 0.95f ? cXyz::BaseZ : cXyz::BaseY; + cXyz axis_z = axis_x.getCrossProduct(helper); + if (!axis_z.normalizeRS()) { + axis_z = cXyz::BaseZ; + } + + cXyz axis_y = axis_z.getCrossProduct(axis_x); + if (!axis_y.normalizeRS()) { + axis_y = cXyz::BaseY; + } + + const cXyz center = anchor_a + ((anchor_b - anchor_a) * 0.5f); + + const cXyz col[3] = { axis_x, axis_y, axis_z }; + const f32 t[3] = { center.x, center.y, center.z }; + for (int r = 0; r < 3; ++r) { + out[r][0] = (&col[0].x)[r]; + out[r][1] = (&col[1].x)[r]; + out[r][2] = (&col[2].x)[r]; + out[r][3] = t[r]; + } +} +#endif + void daMant_packet_c::draw() { #if TARGET_PC void* image = l_Egnd_mantTEX; @@ -290,8 +322,72 @@ void daMant_packet_c::draw() { GXSetVtxAttrFmt(GX_VTXFMT0, GX_VA_NRM, GX_CLR_RGB, GX_F32, 0); GXSetVtxAttrFmt(GX_VTXFMT0, GX_VA_TEX0, GX_CLR_RGBA, GX_F32, 0); +#if TARGET_PC + cXyz* draw_pos = &this->mNrm[0][0]; + { + const u8 curr_buffer = this->field_0x74; + const cXyz* curr_pos = &this->mPos[curr_buffer][0]; + const MtxP curr_frame = curr_buffer == 0 ? this->mMtx : this->mMtx2; + + Mtx curr_frame_inverse; + MTXInverse(curr_frame, curr_frame_inverse); + + const u8 prev_buffer = curr_buffer ^ 1; + const cXyz* prev_pos = &this->mPos[prev_buffer][0]; + Mtx prev_frame_inverse; + MTXInverse(prev_buffer == 0 ? this->mMtx : this->mMtx2, prev_frame_inverse); + + Mtx presented_frame; + MTXCopy(curr_frame, presented_frame); + + mant_class* mant_p = reinterpret_cast(reinterpret_cast(this) - offsetof(mant_class, field_0x0570)); + if (mant_p != NULL) { + b_gnd_class* parent = (b_gnd_class*)fopAcM_SearchByID(mant_p->parentActorID); + if (parent != NULL && parent->mpModelMorf != NULL) { + J3DModel* model = parent->mpModelMorf->getModel(); + if (model != NULL) { + MtxP src34 = model->getAnmMtx(34); + MtxP src25 = model->getAnmMtx(25); + Mtx joint_34_scratch; + Mtx joint_25_scratch; + MtxP joint_34 = dusk::frame_interp::lookup_replacement(src34, joint_34_scratch) ? joint_34_scratch : src34; + MtxP joint_25 = dusk::frame_interp::lookup_replacement(src25, joint_25_scratch) ? joint_25_scratch : src25; + + cXyz presented_anchor_a; + cXyz presented_anchor_b; + cXyz local_offset; + + MTXCopy(joint_34, *calc_mtx); + local_offset.set(10.0f, 5.0f, -17.0f); + MtxPosition(&local_offset, &presented_anchor_a); + + MTXCopy(joint_25, *calc_mtx); + local_offset.set(10.0f, 5.0f, 17.0f); + MtxPosition(&local_offset, &presented_anchor_b); + + mant_build_anchor_frame(presented_anchor_a, presented_anchor_b, presented_frame); + } + } + } + + const f32 step = dusk::frame_interp::get_interpolation_step(); + for (int i = 0; i < 169; ++i) { + cXyz curr_local; + MTXMultVec(curr_frame_inverse, &curr_pos[i], &curr_local); + + cXyz prev_local; + MTXMultVec(prev_frame_inverse, &prev_pos[i], &prev_local); + cXyz local = prev_local + ((curr_local - prev_local) * step); + + MTXMultVec(presented_frame, &local, &draw_pos[i]); + } + } + GXSETARRAY(GX_VA_POS, draw_pos, sizeof(mNrm[0]), 12, true); + GXSETARRAY(GX_VA_NRM, &l_normal, sizeof(l_normal), 12, false); +#else GXSETARRAY(GX_VA_POS, this->getPos(), sizeof(mPos[0]), 12, true); GXSETARRAY(GX_VA_NRM, this->getNrm(), sizeof(mNrm[0]), 12, true); +#endif GXSETARRAY(GX_VA_TEX0, &l_texCoord, sizeof(l_texCoord), 8, false); // TODO: set to true when converted to float literals GXSetZCompLoc(0); @@ -329,9 +425,14 @@ void daMant_packet_c::draw() { GXSetCullMode(GX_CULL_BACK); - GXLoadPosMtxImm(this->mMtx, GX_PNMTX0); Mtx MStack_54; +#if TARGET_PC + GXLoadPosMtxImm(j3dSys.getViewMtx(), GX_PNMTX0); + cMtx_inverseTranspose(j3dSys.getViewMtx(), MStack_54); +#else + GXLoadPosMtxImm(this->mMtx, GX_PNMTX0); cMtx_inverseTranspose(this->mMtx, MStack_54); +#endif GXLoadNrmMtxImm(MStack_54, GX_PNMTX0); GXCallDisplayList(l_Egnd_mantDL, 0x3e0); @@ -347,8 +448,13 @@ void daMant_packet_c::draw() { GXSetTevKColor(GX_KCOLOR0, COMPOUND_LITERAL(GXColor){0, 0, 0, 0}); GXSetCullMode(GX_CULL_FRONT); +#if TARGET_PC + GXLoadPosMtxImm(j3dSys.getViewMtx(), GX_PNMTX0); + cMtx_inverseTranspose(j3dSys.getViewMtx(), MStack_54); +#else GXLoadPosMtxImm(this->mMtx2, GX_PNMTX0); cMtx_inverseTranspose(this->mMtx2, MStack_54); +#endif GXLoadNrmMtxImm(MStack_54, GX_PNMTX0); GXCallDisplayList(l_Egnd_mantDL, 0x3e0); @@ -360,11 +466,13 @@ void daMant_packet_c::draw() { static int daMant_Draw(mant_class* i_this) { g_env_light.settingTevStruct(0, &i_this->current.pos, &i_this->tevStr); +#if !TARGET_PC MtxTrans(0.0f, 0.0f, 0.0f, 0.0f); cMtx_concat(j3dSys.getViewMtx(), *calc_mtx, i_this->field_0x0570.getMtx()); cMtx_concat(j3dSys.getViewMtx(), *calc_mtx, i_this->field_0x0570.getMtx2()); +#endif i_this->field_0x0570.setTevStr(&i_this->tevStr); @@ -635,8 +743,13 @@ static void mant_v_calc(mant_class* i_this) { } static void mant_move(mant_class* i_this) { +#if TARGET_PC + u8 uVar1 = i_this->field_0x0570.field_0x74 ^ 1; + cXyz* pcVar5 = &i_this->field_0x0570.mPos[uVar1][0]; +#else u8 uVar1 = i_this->field_0x0570.field_0x74; cXyz* pcVar5 = i_this->field_0x0570.getPos(); +#endif mant_v_calc(i_this); for (int i = 0; i < 13; i++) { for (int j = 0; j < 13; j++) { @@ -644,7 +757,12 @@ static void mant_move(mant_class* i_this) { } } +#if TARGET_PC + mant_build_anchor_frame(i_this->field_0x3928[0], i_this->field_0x3928[1], uVar1 == 0 ? i_this->field_0x0570.mMtx : i_this->field_0x0570.mMtx2); + i_this->field_0x0570.field_0x74 = uVar1; +#else DCStoreRangeNoSync(i_this->field_0x0570.getPos(), 0x7ec); +#endif } static int mant_cut_type; diff --git a/src/d/actor/d_a_mg_fshop.cpp b/src/d/actor/d_a_mg_fshop.cpp index cb9cc12dea..99f39e410e 100644 --- a/src/d/actor/d_a_mg_fshop.cpp +++ b/src/d/actor/d_a_mg_fshop.cpp @@ -14,6 +14,10 @@ #include "d/d_s_play.h" #include "Z2AudioLib/Z2Instances.h" +#if TARGET_PC +#include "dusk/gyro.h" +#endif + enum koro2_parts { KORO2_PART_BOX = 1, KORO2_PART_CURVE_A_U_L, @@ -723,6 +727,14 @@ static void koro2_game(fshop_class* i_this) { cLib_addCalcAngleS2(&i_this->field_0x4020.x, 0, 2, 0x200); cLib_addCalcAngleS2(&i_this->field_0x4020.z, 0, 2, 0x200); case 2: +#if TARGET_PC + if (dusk::getSettings().game.enableGyroRollgoal) { + if (!dusk::gyro::get_sensor_keep_alive()) { + dusk::gyro::set_sensor_keep_alive(true); + } + } +#endif + actor->scale.x = 10.0f; if (i_this->field_0x4010 == 2) { static f32 old_stick_x = 0.0f; @@ -739,11 +751,21 @@ static void koro2_game(fshop_class* i_this) { old_stick_x = mDoCPd_c::getSubStickX(PAD_1); cLib_addCalcAngleS2(&i_this->field_0x4060, i_this->field_0x4062, 4, 0x1000); +#if TARGET_PC + if (dusk::getSettings().game.enableGyroRollgoal) { + dusk::gyro::rollgoalTick(true, i_this->field_0x4060); + } +#endif cMtx_YrotS(*calc_mtx, -i_this->field_0x4060); sp5C.x = mDoCPd_c::getStickX3D(PAD_1); sp5C.y = 0.0f; sp5C.z = mDoCPd_c::getStickY(PAD_1); +#if TARGET_PC + if (dusk::getSettings().game.enableMirrorMode) { + sp5C.x = -sp5C.x; + } +#endif MtxPosition(&sp5C, &sp68); f32 reg_f31 = sp68.x; @@ -765,9 +787,21 @@ static void koro2_game(fshop_class* i_this) { reg_f30 = 0.0f; } - cLib_addCalcAngleS2(&i_this->field_0x4020.x, reg_f30 * (-6000.0f + JREG_F(7)), 4, 0x200); - cLib_addCalcAngleS2(&i_this->field_0x4020.z, reg_f31 * (-6000.0f + JREG_F(8)), 4, 0x200); + s16 gyro_ax = 0; + s16 gyro_az = 0; +#if TARGET_PC + if (dusk::getSettings().game.enableGyroRollgoal) { + dusk::gyro::rollgoalTableOffset(gyro_ax, gyro_az); + } +#endif + cLib_addCalcAngleS2(&i_this->field_0x4020.x, reg_f30 * (-6000.0f + JREG_F(7)) + gyro_ax, 4, 0x200); + cLib_addCalcAngleS2(&i_this->field_0x4020.z, reg_f31 * (-6000.0f + JREG_F(8)) + gyro_az, 4, 0x200); } +#if TARGET_PC + if (i_this->field_0x4010 != 2) { + dusk::gyro::rollgoalTick(false, i_this->field_0x4060); + } +#endif break; } @@ -1145,6 +1179,12 @@ static int daFshop_Delete(fshop_class* i_this) { } } +#if TARGET_PC + if (dusk::getSettings().game.enableGyroRollgoal) { + dusk::gyro::set_sensor_keep_alive(false); + } +#endif + return 1; } diff --git a/src/d/actor/d_a_midna.cpp b/src/d/actor/d_a_midna.cpp index 01d472e075..7bb844932e 100644 --- a/src/d/actor/d_a_midna.cpp +++ b/src/d/actor/d_a_midna.cpp @@ -1054,9 +1054,9 @@ void daMidna_c::setBodyPartMatrix() { } mpModel->calcWeightEnvelopeMtx(); #ifdef TARGET_PC - // Frame interpolation: Record weight envelopes for Midna here, as they are otherwise missed causing distortion + // FRAME INTERP NOTE: Record weight envelopes for Midna here, as they are otherwise missed causing distortion for (u16 i = 0; i < mpModel->getModelData()->getWEvlpMtxNum(); i++) { - dusk::frame_interp::record_final_mtx_raw(reinterpret_cast(mpModel->getWeightAnmMtx(i)), mpModel->getWeightAnmMtx(i)); + dusk::frame_interp::record_final_mtx(mpModel->getWeightAnmMtx(i)); } #endif } diff --git a/src/d/actor/d_a_mirror.cpp b/src/d/actor/d_a_mirror.cpp index 5468d70f87..fb4142c0bb 100644 --- a/src/d/actor/d_a_mirror.cpp +++ b/src/d/actor/d_a_mirror.cpp @@ -13,6 +13,9 @@ #include #include #include "m_Do/m_Do_lib.h" +#if TARGET_PC +#include "dusk/frame_interpolation.h" +#endif #ifndef __MWERKS__ #include "dusk/math.h" @@ -27,11 +30,19 @@ static char* l_arcName = "Mirror"; static char* l_arcName2 = "MR-Table"; dMirror_packet_c::dMirror_packet_c() { +#ifdef TARGET_PC + GXInitTexObj(&mTexObj, nullptr, 0, 0, static_cast(-1), GX_MAX_TEXWRAPMODE, + GX_MAX_TEXWRAPMODE, GX_FALSE); +#endif reset(); } void dMirror_packet_c::reset() { +#if TARGET_PC + mbReset = true; +#else mModelCount = 0; +#endif } void dMirror_packet_c::calcMinMax() { @@ -73,6 +84,13 @@ void dMirror_packet_c::calcMinMax() { } int dMirror_packet_c::entryModel(J3DModel* i_model) { +#if TARGET_PC + if (mbReset) { + mModelCount = 0; + mbReset = false; + } +#endif + if (mModelCount >= 0x40) { return 0; } diff --git a/src/d/actor/d_a_movie_player.cpp b/src/d/actor/d_a_movie_player.cpp index c3074ac877..079584f714 100644 --- a/src/d/actor/d_a_movie_player.cpp +++ b/src/d/actor/d_a_movie_player.cpp @@ -3342,13 +3342,8 @@ static void daMP_THPGXYuv2RgbSetup(const GXRenderModeObj* rmode) { Mtx44 m; Mtx e_m; -#if TARGET_PC - w = JUTVideo::getManager()->getFbWidth(); - h = JUTVideo::getManager()->getEfbHeight(); -#else w = rmode->fbWidth; h = rmode->efbHeight; -#endif var_f31 = 0.0f; #if WIDESCREEN_SUPPORT @@ -4383,6 +4378,8 @@ static void daMP_ActivePlayer_Draw() { daMP_DrawPosX = static_cast(rect.PosX); daMP_DrawPosY = static_cast(rect.PosY); + + daMP_THPPlayerSetVolume((dusk::getSettings().audio.masterVolume / 100.0f) * 127.0f, 0); #endif int frame = daMP_THPPlayerDrawCurrentFrame( @@ -4583,3 +4580,12 @@ actor_process_profile_definition g_profile_MOVIE_PLAYER = { }; AUDIO_INSTANCES; + +#if TARGET_PC +void dusk::MoviePlayerShutdown() { + // We need to cleanly shut down the threads to avoid crashes on shutdown. + if (daMP_c::m_myObj) { + daMP_c::m_myObj->daMP_c_Finish(); + } +} +#endif \ No newline at end of file diff --git a/src/d/actor/d_a_npc.cpp b/src/d/actor/d_a_npc.cpp index 6ec2fbe7a9..123bf66800 100644 --- a/src/d/actor/d_a_npc.cpp +++ b/src/d/actor/d_a_npc.cpp @@ -2694,12 +2694,7 @@ BOOL daNpcT_chkActorInScreen(fopAc_ac_c* i_ActorP, f32 param_1, f32 param_2, f32 } for (int i = 0; i < 8; i++) { - #if TARGET_PC - mDoLib_project(&pos_array[i], &proj, {0, 0, FB_WIDTH, FB_HEIGHT}); - #else mDoLib_project(&pos_array[i], &proj); - #endif - if (0.0f < proj.x && proj.x < FB_WIDTH && 0.0f < proj.y && proj.y < FB_HEIGHT) { continue; } diff --git a/src/d/actor/d_a_npc_inko.cpp b/src/d/actor/d_a_npc_inko.cpp index 3c277085f6..4d55a31f8e 100644 --- a/src/d/actor/d_a_npc_inko.cpp +++ b/src/d/actor/d_a_npc_inko.cpp @@ -110,6 +110,10 @@ static int daNpc_Inko_Execute(npc_inko_class* i_this) { } f32 var_f31; + + #if AVOID_UB + var_f31 = 0.0f; + #endif if (i_this->field_0x598 == 0) { if (i_this->field_0x59c[1] == 0) { i_this->field_0x59c[1] = 30.0f + cM_rndF(70.0f); diff --git a/src/d/actor/d_a_npc_ne.cpp b/src/d/actor/d_a_npc_ne.cpp index ae43981593..1324c56cf5 100644 --- a/src/d/actor/d_a_npc_ne.cpp +++ b/src/d/actor/d_a_npc_ne.cpp @@ -18,6 +18,7 @@ #include "f_op/f_op_kankyo_mng.h" #include "c/c_damagereaction.h" #include "Z2AudioLib/Z2Instances.h" +#include "dusk/frame_interpolation.h" #include static home_path_pnt home_path[38] = { @@ -2655,6 +2656,9 @@ static void demo_camera(npc_ne_class* i_this) { i_this->mCameraFovY = 55.0f; camera->mCamera.SetTrimSize(3); daPy_getPlayerActorClass()->changeOriginalDemo(); +#ifdef TARGET_PC + dusk::frame_interp::request_presentation_sync(); +#endif // fallthrough case 2: @@ -2683,6 +2687,9 @@ static void demo_camera(npc_ne_class* i_this) { if (i_this->mDemoCounter == 0) { i_this->mCameraCenter1.set(387.0f, 133.0f, -866.0f); i_this->mCameraEye1.set(284.0f, 208.0f, -585.0f); +#ifdef TARGET_PC + dusk::frame_interp::request_presentation_sync(); +#endif } if (i_this->mDemoCounter == 12) { @@ -2719,6 +2726,9 @@ static void demo_camera(npc_ne_class* i_this) { i_this->mCameraFovY = 45.0f; camera->mCamera.SetTrimSize(3); daPy_getPlayerActorClass()->changeOriginalDemo(); +#ifdef TARGET_PC + dusk::frame_interp::request_presentation_sync(); +#endif // fallthrough case 11: @@ -2799,8 +2809,14 @@ static void demo_camera(npc_ne_class* i_this) { MtxPosition(&vec, &i_this->mCameraEye2); i_this->mCameraEye2 += player->current.pos; player->changeDemoParam2(2); +#ifdef TARGET_PC + dusk::frame_interp::request_presentation_sync(); +#endif } else if (i_this->mDemoCounter == 120) { player->changeDemoParam2(0); +#ifdef TARGET_PC + dusk::frame_interp::request_presentation_sync(); +#endif } } } @@ -2853,6 +2869,9 @@ static void demo_camera(npc_ne_class* i_this) { i_this->mCameraCenter1 = _this->current.pos; i_this->mCameraCenter1.y += 20.0f; i_this->mCameraFovY = 55.0f; +#ifdef TARGET_PC + dusk::frame_interp::request_presentation_sync(); +#endif } camera->mCamera.Set(i_this->mCameraCenter1, i_this->mCameraEye1, diff --git a/src/d/actor/d_a_obj_Lv5Key.cpp b/src/d/actor/d_a_obj_Lv5Key.cpp index 9c3b5c0e4c..a71ad25e33 100644 --- a/src/d/actor/d_a_obj_Lv5Key.cpp +++ b/src/d/actor/d_a_obj_Lv5Key.cpp @@ -170,7 +170,7 @@ void daObjLv5Key_c::Fall(int param_0) { OS_REPORT("FALL SPD = %f\n", speed.y); - if (mAcch.ChkGroundHit()) { + if (mAcch.ChkGroundHit() IF_DUSK(|| current.pos.abs(home.pos) > 200.0f)) { fopAcM_GetSpeed(this); fopAcM_SetSpeedF(this, 4.0f); fopAcM_SetSpeed(this, 0.0f, 22.0f, 0.0f); @@ -192,7 +192,7 @@ void daObjLv5Key_c::Fall(int param_0) { mAcch.CrrPos(dComIfG_Bgsp()); current.pos.y = prev_y; - if (mAcch.ChkGroundHit()) { + if (mAcch.ChkGroundHit() IF_DUSK(|| current.pos.abs(home.pos) > 200.0f)) { setAction(&daObjLv5Key_c::Land, 1); } } diff --git a/src/d/actor/d_a_obj_ari.cpp b/src/d/actor/d_a_obj_ari.cpp index 3e2727e2f2..267b0003af 100644 --- a/src/d/actor/d_a_obj_ari.cpp +++ b/src/d/actor/d_a_obj_ari.cpp @@ -499,13 +499,7 @@ void daObjARI_c::Z_BufferChk() { cXyz vec2, vec1; vec1 = current.pos; vec1.y += 20.0f; - - #if TARGET_PC - mDoLib_project(&vec1, &vec2, {0, 0, FB_WIDTH, FB_HEIGHT}); - #else mDoLib_project(&vec1, &vec2); - #endif - f32 trim_height; camera_process_class* camera = dComIfGp_getCamera(0); if (camera != NULL) { diff --git a/src/d/actor/d_a_obj_bhashi.cpp b/src/d/actor/d_a_obj_bhashi.cpp index 33595c7a77..00a35bc158 100644 --- a/src/d/actor/d_a_obj_bhashi.cpp +++ b/src/d/actor/d_a_obj_bhashi.cpp @@ -285,13 +285,7 @@ bool Hahen_c::CheckCull() { bool Hahen_c::checkViewArea() { Vec proj; - - #if TARGET_PC - mDoLib_project(&pos, &proj, {0, 0, FB_WIDTH, FB_HEIGHT}); - #else mDoLib_project(&pos, &proj); - #endif - return (proj.x >= -50.0f && proj.x <= 658.0f) && (proj.y >= -50.0f && proj.y <= 498.0f); } diff --git a/src/d/actor/d_a_obj_cho.cpp b/src/d/actor/d_a_obj_cho.cpp index 27b0b8ce0e..8fbc2dbde8 100644 --- a/src/d/actor/d_a_obj_cho.cpp +++ b/src/d/actor/d_a_obj_cho.cpp @@ -289,13 +289,7 @@ void daObjCHO_c::Z_BufferChk() { cXyz vec2, vec1; vec1 = current.pos; vec1.y += 20.0f; - - #if TARGET_PC - mDoLib_project(&vec1, &vec2, {0, 0, FB_WIDTH, FB_HEIGHT}); - #else mDoLib_project(&vec1, &vec2); - #endif - f32 trim_height; camera_process_class* camera = dComIfGp_getCamera(0); if (camera != NULL) { diff --git a/src/d/actor/d_a_obj_crvfence.cpp b/src/d/actor/d_a_obj_crvfence.cpp index 03ff2b1d6a..9712205ea3 100644 --- a/src/d/actor/d_a_obj_crvfence.cpp +++ b/src/d/actor/d_a_obj_crvfence.cpp @@ -224,13 +224,7 @@ void daObjCRVFENCE_c::NormalAction() { bool daObjCRVFENCE_c::checkViewArea(cXyz* param_1) { Vec sp24; - - #if TARGET_PC - mDoLib_project(param_1, &sp24, {0, 0, FB_WIDTH, FB_HEIGHT}); - #else mDoLib_project(param_1, &sp24); - #endif - bool rv = false; bool bVar1 = false; diff --git a/src/d/actor/d_a_obj_crvhahen.cpp b/src/d/actor/d_a_obj_crvhahen.cpp index 0b44a57ec8..2dc91c6e6b 100644 --- a/src/d/actor/d_a_obj_crvhahen.cpp +++ b/src/d/actor/d_a_obj_crvhahen.cpp @@ -137,13 +137,7 @@ void daObjCRVHAHEN_c::CheckCull() { bool daObjCRVHAHEN_c::checkViewArea(cXyz* i_this) { Vec proj; - - #if TARGET_PC - mDoLib_project(i_this, &proj, {0, 0, FB_WIDTH, FB_HEIGHT}); - #else mDoLib_project(i_this, &proj); - #endif - bool ret = false; if (proj.x >= 0.0f && proj.x <= FB_WIDTH && proj.y >= 0.0f && proj.y <= FB_HEIGHT) { diff --git a/src/d/actor/d_a_obj_dan.cpp b/src/d/actor/d_a_obj_dan.cpp index 53dbf4e1e4..cc176b9a20 100644 --- a/src/d/actor/d_a_obj_dan.cpp +++ b/src/d/actor/d_a_obj_dan.cpp @@ -267,13 +267,7 @@ void daObjDAN_c::Z_BufferChk() { cXyz vec2, vec1; vec1 = current.pos; vec1.y += 20.0f; - - #if TARGET_PC - mDoLib_project(&vec1, &vec2, {0, 0, FB_WIDTH, FB_HEIGHT}); - #else mDoLib_project(&vec1, &vec2); - #endif - f32 trim_height; camera_process_class* camera = dComIfGp_getCamera(0); if (camera != NULL) { diff --git a/src/d/actor/d_a_obj_gomikabe.cpp b/src/d/actor/d_a_obj_gomikabe.cpp index a5b58a44a5..66963f0efd 100644 --- a/src/d/actor/d_a_obj_gomikabe.cpp +++ b/src/d/actor/d_a_obj_gomikabe.cpp @@ -201,13 +201,7 @@ void daObjGOMIKABE_c::CheckCull() { bool daObjGOMIKABE_c::checkViewArea(cXyz param_1) { Vec local_24; - - #if TARGET_PC - mDoLib_project(¶m_1, &local_24, {0, 0, FB_WIDTH, FB_HEIGHT}); - #else mDoLib_project(¶m_1, &local_24); - #endif - bool rv = false; if (local_24.x >= 0.0f && local_24.x <= FB_WIDTH && local_24.y >= 0.0f && local_24.y <= FB_HEIGHT) { rv = true; diff --git a/src/d/actor/d_a_obj_hhashi.cpp b/src/d/actor/d_a_obj_hhashi.cpp index ea45fa5ea1..d5ab5558b0 100644 --- a/src/d/actor/d_a_obj_hhashi.cpp +++ b/src/d/actor/d_a_obj_hhashi.cpp @@ -214,13 +214,7 @@ void daObjHHASHI_c::CheckCull() { bool daObjHHASHI_c::checkViewArea(int param_1) { Vec local_20; - - #if TARGET_PC - mDoLib_project(&field_0x5b0[param_1], &local_20, {0, 0, FB_WIDTH, FB_HEIGHT}); - #else mDoLib_project(&field_0x5b0[param_1], &local_20); - #endif - bool rv = false; if (local_20.x >= 0.0f && local_20.x <= FB_WIDTH && local_20.y >= 0.0f && local_20.y <= FB_HEIGHT) { rv = true; diff --git a/src/d/actor/d_a_obj_kamakiri.cpp b/src/d/actor/d_a_obj_kamakiri.cpp index daa6560e6f..2b641d7769 100644 --- a/src/d/actor/d_a_obj_kamakiri.cpp +++ b/src/d/actor/d_a_obj_kamakiri.cpp @@ -517,13 +517,7 @@ void daObjKAM_c::Z_BufferChk() { cXyz currentOffset; currentOffset = current.pos; currentOffset.y += 20.0f; - - #if TARGET_PC - mDoLib_project(¤tOffset, ¤tProj, {0, 0, FB_WIDTH, FB_HEIGHT}); - #else mDoLib_project(¤tOffset, ¤tProj); - #endif - camera_process_class* camera = dComIfGp_getCamera(0); f32 cameraHeight; if (camera != NULL) { diff --git a/src/d/actor/d_a_obj_katatsumuri.cpp b/src/d/actor/d_a_obj_katatsumuri.cpp index 03d40a1066..805aa37b44 100644 --- a/src/d/actor/d_a_obj_katatsumuri.cpp +++ b/src/d/actor/d_a_obj_katatsumuri.cpp @@ -611,13 +611,7 @@ void daObjKAT_c::Z_BufferChk() { cXyz curWithOff; curWithOff = current.pos; curWithOff.y += 20.0f; - - #if TARGET_PC - mDoLib_project(&curWithOff, &projected, {0, 0, FB_WIDTH, FB_HEIGHT}); - #else mDoLib_project(&curWithOff, &projected); - #endif - camera_process_class* camera = dComIfGp_getCamera(0); f32 unkFloat1; if (camera != NULL) { diff --git a/src/d/actor/d_a_obj_kuwagata.cpp b/src/d/actor/d_a_obj_kuwagata.cpp index 914e25dfb1..80020208d3 100644 --- a/src/d/actor/d_a_obj_kuwagata.cpp +++ b/src/d/actor/d_a_obj_kuwagata.cpp @@ -528,13 +528,7 @@ void daObjKUW_c::Z_BufferChk() { cStack_68 = current.pos; cStack_68.y += 20.0f; - - #if TARGET_PC - mDoLib_project(&cStack_68, &local_5c, {0, 0, FB_WIDTH, FB_HEIGHT}); - #else mDoLib_project(&cStack_68, &local_5c); - #endif - camera_process_class* cc = dComIfGp_getCamera(0); f32 trimHeight; if (cc != NULL) { diff --git a/src/d/actor/d_a_obj_stone.cpp b/src/d/actor/d_a_obj_stone.cpp index f74adf12a7..080069e4f0 100644 --- a/src/d/actor/d_a_obj_stone.cpp +++ b/src/d/actor/d_a_obj_stone.cpp @@ -981,9 +981,7 @@ int daObjStone_c::draw() { if (!model) { f32 shadow_size = l_shadow_size[mStoneType]; TGXTexObj* pTex = dDlst_shadowControl_c::getSimpleTex(); - cXyz pos = current.pos; - - dComIfGd_setSimpleShadow(&pos, mChkObj.GetGroundH(), shadow_size, mChkObj.m_gnd, 0, + dComIfGd_setSimpleShadow(¤t.pos, mChkObj.GetGroundH(), shadow_size, mChkObj.m_gnd, 0, 1.0f, pTex); } return 1; diff --git a/src/d/actor/d_a_obj_ten.cpp b/src/d/actor/d_a_obj_ten.cpp index 1efe358607..fbedd6a8c9 100644 --- a/src/d/actor/d_a_obj_ten.cpp +++ b/src/d/actor/d_a_obj_ten.cpp @@ -593,13 +593,7 @@ void daObjTEN_c::Z_BufferChk() { cXyz cStack_68; cStack_68 = current.pos; cStack_68.y += 20.0f; - - #if TARGET_PC - mDoLib_project(&cStack_68, &local_5c, {0, 0, FB_WIDTH, FB_HEIGHT}); - #else mDoLib_project(&cStack_68, &local_5c); - #endif - camera_process_class* camera = dComIfGp_getCamera(0); f32 trimHeight; if (camera != NULL) { diff --git a/src/d/actor/d_a_obj_tombo.cpp b/src/d/actor/d_a_obj_tombo.cpp index 6fcbc938d9..a518e3cf25 100644 --- a/src/d/actor/d_a_obj_tombo.cpp +++ b/src/d/actor/d_a_obj_tombo.cpp @@ -504,13 +504,7 @@ void daObjTOMBO_c::Z_BufferChk() { cXyz cStack_68; cStack_68 = current.pos; cStack_68.y += 20.0f; - - #if TARGET_PC - mDoLib_project(&cStack_68, &local_5c, {0, 0, FB_WIDTH, FB_HEIGHT}); - #else mDoLib_project(&cStack_68, &local_5c); - #endif - camera_process_class* pCamera = dComIfGp_getCamera(0); f32 trimHeight; if (pCamera != NULL) { diff --git a/src/d/actor/d_a_obj_zra_freeze.cpp b/src/d/actor/d_a_obj_zra_freeze.cpp index 08932c439d..1525bdb572 100644 --- a/src/d/actor/d_a_obj_zra_freeze.cpp +++ b/src/d/actor/d_a_obj_zra_freeze.cpp @@ -38,12 +38,7 @@ BOOL daZraFreeze_c::chkActorInScreen() { mDoMtx_stack_c::transM(0.0f, 0.0f, 0.0f); PSMTXMultVecArray(mDoMtx_stack_c::get(), vec, vec, 8); for (int i = 0; i < 8; i++) { - #if TARGET_PC - mDoLib_project(&vec[i], &proj, {0, 0, FB_WIDTH, FB_HEIGHT}); - #else mDoLib_project(&vec[i], &proj); - #endif - if (0.0f < proj.x && proj.x < FB_WIDTH && 0.0f < proj.y && proj.y < FB_HEIGHT) { continue; } diff --git a/src/d/actor/d_a_player.cpp b/src/d/actor/d_a_player.cpp index 4c8b31c4c6..8ceb0ef237 100644 --- a/src/d/actor/d_a_player.cpp +++ b/src/d/actor/d_a_player.cpp @@ -421,13 +421,7 @@ void daPy_sightPacket_c::draw() { void daPy_sightPacket_c::setSight() { Vec proj; - - #if TARGET_PC - mDoLib_project(&mPos, &proj, {0, 0, FB_WIDTH, FB_HEIGHT}); - #else mDoLib_project(&mPos, &proj); - #endif - mDoMtx_stack_c::transS(proj.x, proj.y, proj.z); mDoMtx_stack_c::scaleM(32.0f, 32.0f, 32.0f); mDoMtx_copy(mDoMtx_stack_c::get(), mProjMtx); diff --git a/src/d/actor/d_flower.inc b/src/d/actor/d_flower.inc index 034d35961e..58daa354a1 100644 --- a/src/d/actor/d_flower.inc +++ b/src/d/actor/d_flower.inc @@ -699,14 +699,14 @@ void dFlower_packet_c::draw() { if (!cLib_checkBit(sp44->m_state, 4) && !cLib_checkBit(sp44->m_state, 0x40)) { #ifdef TARGET_PC Mtx flower_mtx; - if (dusk::frame_interp::lookup_replacement(reinterpret_cast(&sp44->m_modelMtx), flower_mtx)) { + if (dusk::frame_interp::lookup_replacement(&sp44->m_modelMtx, flower_mtx)) { + cMtx_concat(j3dSys.getViewMtx(), flower_mtx, flower_mtx); GXLoadPosMtxImm(flower_mtx, 0); - } else { + } else #endif + { GXLoadPosMtxImm(sp44->m_modelMtx, 0); -#ifdef TARGET_PC } -#endif GXLoadNrmMtxImm(j3dSys.getViewMtx(), 0); #if TARGET_PC @@ -854,20 +854,18 @@ void dFlower_packet_c::draw() { if (!cLib_checkBit(sp34->m_state, 4) && cLib_checkBit(sp34->m_state, 0x40)) { #ifdef TARGET_PC Mtx flower_mtx; - if (dusk::frame_interp::lookup_replacement(reinterpret_cast(&sp34->m_modelMtx), flower_mtx)) { + if (dusk::frame_interp::lookup_replacement(&sp34->m_modelMtx, flower_mtx)) { + cMtx_concat(j3dSys.getViewMtx(), flower_mtx, flower_mtx); GXLoadPosMtxImm(flower_mtx, 0); - } else { + } else #endif + { GXLoadPosMtxImm(sp34->m_modelMtx, 0); -#ifdef TARGET_PC } -#endif GXLoadNrmMtxImm(j3dSys.getViewMtx(), 0); - #if TARGET_PC GXLoadTexObj(&mTexObj_l_J_Ohana01_64128_0419TEX, GX_TEXMAP0); #endif - if (!cLib_checkBit(sp34->m_state, 8)) { if (!cLib_checkBit(sp34->m_state, 0x10)) { GXCallDisplayList(mp_Jhana01DL, m_Jhana01DL_size); @@ -994,7 +992,7 @@ void dFlower_packet_c::update() { mDoMtx_stack_c::scaleM(temp_f31, temp_f31, temp_f31); cMtx_concat(j3dSys.getViewMtx(), temp_r28, data_p->m_modelMtx); #ifdef TARGET_PC - dusk::frame_interp::record_final_mtx_raw(reinterpret_cast(&data_p->m_modelMtx), data_p->m_modelMtx); + dusk::frame_interp::record_final_mtx(temp_r28, data_p->m_modelMtx); #endif } } diff --git a/src/d/actor/d_grass.inc b/src/d/actor/d_grass.inc index 0f651a3203..ab2724060f 100644 --- a/src/d/actor/d_grass.inc +++ b/src/d/actor/d_grass.inc @@ -756,13 +756,13 @@ void dGrass_packet_c::draw() { #ifdef TARGET_PC Mtx grass_mtx; if (dusk::frame_interp::lookup_replacement(reinterpret_cast(&var_r29->m_modelMtx), grass_mtx)) { + cMtx_concat(j3dSys.getViewMtx(), grass_mtx, grass_mtx); GXLoadPosMtxImm(grass_mtx, 0); - } else { + } else #endif + { GXLoadPosMtxImm(var_r29->m_modelMtx, 0); -#ifdef TARGET_PC } -#endif GXLoadNrmMtxImm(j3dSys.getViewMtx(), 0); if (var_r29->field_0x05 <= 3 || var_r29->field_0x05 >= 10) { if (var_r29->field_0x02 < -1) { @@ -1018,7 +1018,7 @@ void dGrass_packet_c::update() { cMtx_concat(j3dSys.getViewMtx(), mDoMtx_stack_c::get(), data_p->m_modelMtx); } #ifdef TARGET_PC - dusk::frame_interp::record_final_mtx_raw(reinterpret_cast(&data_p->m_modelMtx), data_p->m_modelMtx); + dusk::frame_interp::record_final_mtx(mDoMtx_stack_c::get(), data_p->m_modelMtx); #endif } } diff --git a/src/d/d_bright_check.cpp b/src/d/d_bright_check.cpp index ac28e46838..c3fcc806d5 100644 --- a/src/d/d_bright_check.cpp +++ b/src/d/d_bright_check.cpp @@ -147,7 +147,7 @@ void dBrightCheck_c::modeMove() { void dBrightCheck_c::brightCheckWide() { // Main Canvas mBrightCheck.Scr->scale(mDoGph_gInf_c::hudAspectScaleUp, 1.0f); - mBrightCheck.Scr->translate(mDoGph_gInf_c::getMinXF(), 0.0f); + mBrightCheck.Scr->translate(mDoGph_gInf_c::getSafeMinXF(), 0.0f); // Right Square mBrightCheck.Scr->search(MULTI_CHAR('fuchi_1'))->scale(mDoGph_gInf_c::hudAspectScaleDown, 1.0f); diff --git a/src/d/d_camera.cpp b/src/d/d_camera.cpp index 7b95a0d8ea..f805a6a08b 100644 --- a/src/d/d_camera.cpp +++ b/src/d/d_camera.cpp @@ -7063,6 +7063,15 @@ bool dCamera_c::subjectCamera(s32 param_0) { } cXyz sp1E0(val0, val2, val1); + +#if TARGET_PC + f32 aspect = mDoGph_gInf_c::getAspect(); + f32 baseAspect = FB_WIDTH / FB_HEIGHT; + if (aspect > baseAspect) { + sp1E0.z += (aspect - baseAspect) * 4; + } +#endif + sp1D4 = dCamMath::xyzRotateX(sp1E0, angle_x); sp1E0 = dCamMath::xyzRotateY(sp1D4, angle_y); f32 sp6C = sp12 ? 40.0f : 0.0f; @@ -11009,6 +11018,15 @@ static int camera_execute(camera_process_class* i_this) { i_this->mCamera.CalcTrimSize(); store(i_this); + +#ifdef TARGET_PC + // record new camera for our sim frame + dusk::frame_interp::record_camera(i_this, get_camera_id(i_this)); + // interpolate the view now so that this sim frame's view matrix matches what + // we'll be rendering with later + dusk::frame_interp::interp_view(&i_this->view); +#endif + view_setup(i_this); return 1; } @@ -11077,10 +11095,6 @@ static int camera_draw(camera_process_class* i_this) { C_MTXPerspective(process->view.projMtx, process->view.fovy, process->view.aspect, process->view.near_, process->view.far_); mDoMtx_lookAt(process->view.viewMtx, &process->view.lookat.eye, &process->view.lookat.center, &process->view.lookat.up, process->view.bank); -#ifdef TARGET_PC - dusk::frame_interp::record_camera(process, camera_id); - dusk::frame_interp::record_final_mtx_raw(reinterpret_cast(process->view.viewMtx), process->view.viewMtx); -#endif #if WIDESCREEN_SUPPORT mDoGph_gInf_c::setWideZoomProjection(process->view.projMtx); diff --git a/src/d/d_drawlist.cpp b/src/d/d_drawlist.cpp index e903e39ce4..373791f994 100644 --- a/src/d/d_drawlist.cpp +++ b/src/d/d_drawlist.cpp @@ -1096,16 +1096,7 @@ void dDlst_shadowReal_c::draw() { GXSetVtxDesc(GX_VA_POS, GX_DIRECT); GXSetVtxAttrFmt(GX_VTXFMT0, GX_VA_POS, GX_POS_XYZ, GX_F32, 0); GXSetCurrentMtx(GX_PNMTX0); -#ifdef TARGET_PC - Mtx receiver_proj_mtx; - if (dusk::frame_interp::lookup_replacement(&mReceiverProjMtx, receiver_proj_mtx)) { - GXLoadTexMtxImm(receiver_proj_mtx, GX_TEXMTX0, GX_MTX3x4); - } else { -#endif - GXLoadTexMtxImm(mReceiverProjMtx, GX_TEXMTX0, GX_MTX3x4); -#ifdef TARGET_PC - } -#endif + GXLoadTexMtxImm(mReceiverProjMtx, GX_TEXMTX0, GX_MTX3x4); mShadowRealPoly.draw(); } @@ -1263,14 +1254,9 @@ u8 dDlst_shadowReal_c::setShadowRealMtx(cXyz* param_0, cXyz* param_1, f32 param_ C_MTXOrtho(mRenderProjMtx, param_2, -param_2, -param_2, param_2, 1.0f, 10000.0f); C_MTXLightOrtho(mReceiverProjMtx, param_2, -param_2, -param_2, param_2, 0.5f, -0.5f, 0.5f, 0.5f); cMtx_concat(mReceiverProjMtx, mViewMtx, mReceiverProjMtx); -#ifdef TARGET_PC - dusk::frame_interp::record_final_mtx_raw(&mViewMtx, mViewMtx); - dusk::frame_interp::record_final_mtx_raw(&mReceiverProjMtx, mReceiverProjMtx); -#endif return r29; } - u32 dDlst_shadowReal_c::set(u32 i_key, J3DModel* i_model, cXyz* param_2, f32 param_3, f32 param_4, dKy_tevstr_c* param_5, f32 i_cameraZ, f32 param_7) { dScnKy_env_light_c* env_light = dKy_getEnvlight(); @@ -1292,6 +1278,7 @@ u32 dDlst_shadowReal_c::set(u32 i_key, J3DModel* i_model, cXyz* param_2, f32 par } field_0x1 = setShadowRealMtx(&sp60, param_2, param_3, param_4, param_7, param_5); + if (!field_0x1) { return 0; } @@ -1331,14 +1318,14 @@ void dDlst_shadowSimple_c::draw() { GXSetVtxDesc(GX_VA_POS, GX_INDEX8); #ifdef TARGET_PC Mtx volume_mtx; - if (dusk::frame_interp::lookup_replacement(&mVolumeMtx, volume_mtx)) { + if (dusk::frame_interp::lookup_replacement(mVolumeMtxKey, volume_mtx)) { + cMtx_concat(j3dSys.getViewMtx(), volume_mtx, volume_mtx); GXLoadPosMtxImm(volume_mtx, GX_PNMTX0); - } else { + } else #endif + { GXLoadPosMtxImm(mVolumeMtx, GX_PNMTX0); -#ifdef TARGET_PC } -#endif GXSetCurrentMtx(GX_PNMTX0); GXCallDisplayList(l_frontMat, 0x40); GXCallDisplayList(l_shadowVolumeDL, 0x40); @@ -1346,14 +1333,14 @@ void dDlst_shadowSimple_c::draw() { GXCallDisplayList(l_shadowVolumeDL, 0x40); #ifdef TARGET_PC Mtx shadow_mtx; - if (dusk::frame_interp::lookup_replacement(&mMtx, shadow_mtx)) { + if (dusk::frame_interp::lookup_replacement(mMtxKey, shadow_mtx)) { + cMtx_concat(j3dSys.getViewMtx(), shadow_mtx, shadow_mtx); GXLoadPosMtxImm(shadow_mtx, GX_PNMTX1); - } else { + } else #endif + { GXLoadPosMtxImm(mMtx, GX_PNMTX1); -#ifdef TARGET_PC } -#endif GXSetCurrentMtx(GX_PNMTX1); if (mpTexObj != NULL) { @@ -1383,6 +1370,12 @@ void dDlst_shadowSimple_c::draw() { GXCallDisplayList(l_shadowVolumeDL, 0x40); } +#if TARGET_PC +static const void* getInterpKey(const void* base, int idx) { + return reinterpret_cast(reinterpret_cast(base) ^ idx); +} +#endif + void dDlst_shadowSimple_c::set(cXyz* param_0, f32 param_1, f32 param_2, cXyz* param_3, s16 param_4, f32 param_5, TGXTexObj* param_6) { if (param_5 < 0.0f) { @@ -1406,6 +1399,10 @@ void dDlst_shadowSimple_c::set(cXyz* param_0, f32 param_1, f32 param_2, cXyz* pa mDoMtx_stack_c::transS(param_0->x, param_1 + f30, param_0->z); mDoMtx_stack_c::YrotM(param_4); mDoMtx_stack_c::scaleM(param_2, f30 + f30 + 16.0f, param_2 * param_5); +#if TARGET_PC + mVolumeMtxKey = getInterpKey(param_0, 0x1); + dusk::frame_interp::record_final_mtx(mDoMtx_stack_c::get(), mVolumeMtxKey); +#endif cMtx_concat(j3dSys.getViewMtx(), mDoMtx_stack_c::get(), mVolumeMtx); f32 f31 = JMAFastSqrt(1.0f - param_3->x * param_3->x); f32 f29; @@ -1431,17 +1428,11 @@ void dDlst_shadowSimple_c::set(cXyz* param_0, f32 param_1, f32 param_2, cXyz* pa mDoMtx_stack_c::get()[2][3] = param_0->z; mDoMtx_stack_c::YrotM(param_4); mDoMtx_stack_c::scaleM(param_2, 1.0f, param_2 * param_5); - cMtx_concat(j3dSys.getViewMtx(), mDoMtx_stack_c::get(), mMtx); #ifdef TARGET_PC - const uint64_t shadow_tag_base = dusk::frame_interp::alloc_simple_shadow_pair_base(); - if (shadow_tag_base != 0) { - dusk::frame_interp::record_final_mtx_raw_tagged(&mVolumeMtx, mVolumeMtx, shadow_tag_base); - dusk::frame_interp::record_final_mtx_raw_tagged(&mMtx, mMtx, shadow_tag_base + 1u); - } else { - dusk::frame_interp::record_final_mtx_raw(&mVolumeMtx, mVolumeMtx); - dusk::frame_interp::record_final_mtx_raw(&mMtx, mMtx); - } + mMtxKey = getInterpKey(param_0, 0x2); + dusk::frame_interp::record_final_mtx(mDoMtx_stack_c::get(), mMtxKey); #endif + cMtx_concat(j3dSys.getViewMtx(), mDoMtx_stack_c::get(), mMtx); mpTexObj = param_6; } @@ -1544,9 +1535,10 @@ void dDlst_shadowControl_c::imageDraw(Mtx param_0) { #ifdef TARGET_PC GXCreateFrameBuffer(r26, r26); needsRestore = true; -#endif +#else GXSetViewport(0.0f, 0.0f, r26, r26, 0.0f, 1.0f); GXSetScissor(0, 0, r26, r26); +#endif } GXSetTevColor(GX_TEVREG0, l_imageDrawColor[chan]); if (chan == 3) { diff --git a/src/d/d_file_select.cpp b/src/d/d_file_select.cpp index be40b13d1c..17d5f2c4e4 100644 --- a/src/d/d_file_select.cpp +++ b/src/d/d_file_select.cpp @@ -70,11 +70,7 @@ dFs_HIO_c::dFs_HIO_c() { select_icon_appear_frames = 5; appear_display_wait_frames = 15; field_0x000d = 15; - #if TARGET_PC - card_wait_frames = 0; - #else card_wait_frames = 90; - #endif test_frame_counts[0] = 1.11f; test_frame_counts[1] = 1.11f; test_frame_counts[2] = 1.11f; @@ -2102,11 +2098,7 @@ void dFile_select_c::yesnoCursorShow() { mSelIcon->setPos(pos.x, pos.y, mYnSelPane[field_0x0268]->getPanePtr(), true); mSelIcon->setAlphaRate(1.0f); - #if TARGET_PC - mSelIcon->setParam(0.96f * mDoGph_gInf_c::hudAspectScaleUp, 0.84f, 0.06f, 0.5f, 0.5f); - #else mSelIcon->setParam(0.96f, 0.84f, 0.06f, 0.5f, 0.5f); - #endif } } @@ -2259,11 +2251,7 @@ void dFile_select_c::YesNoCancelMove() { m3mSelPane[mSelectMenuNum]->getPanePtr(), true); mSelIcon->setAlphaRate(1.0f); - #if TARGET_PC - mSelIcon->setParam(0.96f * mDoGph_gInf_c::hudAspectScaleUp, 0.84f, 0.06f, 0.5f, 0.5f); - #else mSelIcon->setParam(0.96f, 0.84f, 0.06f, 0.5f, 0.5f); - #endif #if PLATFORM_WII || PLATFORM_SHIELD field_0x4333 = mSelectMenuNum; @@ -2375,7 +2363,7 @@ void dFile_select_c::CommandExec() { break; } - mWaitTimer = g_fsHIO.card_wait_frames; + mWaitTimer = IF_DUSK(dusk::getSettings().game.instantSaves ? 0 :) g_fsHIO.card_wait_frames; } void dFile_select_c::DataEraseWait() { @@ -3147,11 +3135,7 @@ void dFile_select_c::screenSet() { mSelIcon = JKR_NEW dSelect_cursor_c(0, 1.0f, NULL); JUT_ASSERT(5209, mSelIcon != NULL); - #if TARGET_PC - mSelIcon->setParam(0.96f * mDoGph_gInf_c::hudAspectScaleUp, 0.94f, 0.03f, 0.7f, 0.7f); - #else mSelIcon->setParam(0.96f, 0.94f, 0.03f, 0.7f, 0.7f); - #endif Vec vtxCenter; vtxCenter = mSelFilePanes[mSelectNum]->getGlobalVtxCenter(false, 0); @@ -3204,6 +3188,9 @@ void dFile_select_c::screenSet() { timg, NULL); mpFadePict->setBlackWhite(black, white); mpFadePict->setAlpha(0); +#ifdef TARGET_PC + mFadeDlst.mpPict = mpFadePict; +#endif #endif } @@ -3284,11 +3271,7 @@ void dFile_select_c::screenSetCopySel() { mSelIcon2 = JKR_NEW dSelect_cursor_c(0, 1.0f, NULL); JUT_ASSERT(5406, mSelIcon2 != NULL); - #if TARGET_PC - mSelIcon2->setParam(0.96f * mDoGph_gInf_c::hudAspectScaleUp, 0.94f, 0.03f, 0.7f, 0.7f); - #else mSelIcon2->setParam(0.96f, 0.94f, 0.03f, 0.7f, 0.7f); - #endif Vec center = mCpSelPane[0]->getGlobalVtxCenter(false, 0); mSelIcon2->setPos(center.x, center.y, mCpSelPane[0]->getPanePtr(), true); @@ -3680,11 +3663,7 @@ void dFile_select_c::selFileCursorShow() { mSelIcon->setPos(local_1c.x, local_1c.y, mSelFilePanes[mSelectNum]->getPanePtr(), true); mSelIcon->setAlphaRate(1.0f); - #if TARGET_PC - mSelIcon->setParam(0.96f * mDoGph_gInf_c::hudAspectScaleUp, 0.94f, 0.03f, 0.7f, 0.7f); - #else mSelIcon->setParam(0.96f, 0.94f, 0.03f, 0.7f, 0.7f); - #endif } void dFile_select_c::menuWakuAlpahAnmInit(u8 i_idx, u8 param_1, u8 param_2, u8 param_3) { @@ -3727,11 +3706,7 @@ void dFile_select_c::menuCursorShow() { mSelIcon->setPos(local_24.x, local_24.y, m3mSelPane[mSelectMenuNum]->getPanePtr(), true); mSelIcon->setAlphaRate(1.0f); - #if TARGET_PC - mSelIcon->setParam(0.96f * mDoGph_gInf_c::hudAspectScaleUp, 0.84f, 0.06f, 0.5f, 0.5f); - #else mSelIcon->setParam(0.96f, 0.84f, 0.06f, 0.5f, 0.5f); - #endif } } @@ -3776,7 +3751,7 @@ bool dFile_select_c::yesnoWakuAlpahAnm(u8 param_1) { #if TARGET_PC void dFile_select_c::fileSelectWide() { mYnSel.ScrYn->scale(mDoGph_gInf_c::hudAspectScaleUp, 1.0f); - mYnSel.ScrYn->translate(mDoGph_gInf_c::getMinXF(), 0.0f); + mYnSel.ScrYn->translate(mDoGph_gInf_c::getSafeMinXF(), 0.0f); mYnSel.ScrYn->search(MULTI_CHAR('w_no_t'))->scale(mDoGph_gInf_c::hudAspectScaleDown, 1.0f); mYnSel.ScrYn->search(MULTI_CHAR('f_no_t'))->scale(mDoGph_gInf_c::hudAspectScaleDown, 1.0f); @@ -3784,7 +3759,7 @@ void dFile_select_c::fileSelectWide() { mYnSel.ScrYn->search(MULTI_CHAR('f_yes_t'))->scale(mDoGph_gInf_c::hudAspectScaleDown, 1.0f); m3mSel.Scr3m->scale(mDoGph_gInf_c::hudAspectScaleUp, 1.0f); - m3mSel.Scr3m->translate(mDoGph_gInf_c::getMinXF(), 0.0f); + m3mSel.Scr3m->translate(mDoGph_gInf_c::getSafeMinXF(), 0.0f); m3mSel.Scr3m->search(MULTI_CHAR('w_sta'))->scale(mDoGph_gInf_c::hudAspectScaleDown, 1.0f); m3mSel.Scr3m->search(MULTI_CHAR('f_sta'))->scale(mDoGph_gInf_c::hudAspectScaleDown, 1.0f); @@ -3794,7 +3769,7 @@ void dFile_select_c::fileSelectWide() { m3mSel.Scr3m->search(MULTI_CHAR('f_cop_t'))->scale(mDoGph_gInf_c::hudAspectScaleDown, 1.0f); fileSel.Scr->scale(mDoGph_gInf_c::hudAspectScaleUp, 1.0f); - fileSel.Scr->translate(mDoGph_gInf_c::getMinXF(), 0.0f); + fileSel.Scr->translate(mDoGph_gInf_c::getSafeMinXF(), 0.0f); fileSel.Scr->search(MULTI_CHAR('t_for'))->scale(mDoGph_gInf_c::hudAspectScaleDown, 1.0f); fileSel.Scr->search(MULTI_CHAR('t_for1'))->scale(mDoGph_gInf_c::hudAspectScaleDown, 1.0f); @@ -3833,6 +3808,16 @@ void dFile_select_c::fileSelectWide() { fileSel.Scr->search(MULTI_CHAR('w_uzu07'))->scale(mDoGph_gInf_c::hudAspectScaleDown, 1.0f); fileSel.Scr->search(MULTI_CHAR('w_uzu08'))->scale(mDoGph_gInf_c::hudAspectScaleDown, 1.0f); fileSel.Scr->search(MULTI_CHAR('w_uzu09'))->scale(mDoGph_gInf_c::hudAspectScaleDown, 1.0f); + + #if TARGET_PC + if (mSelIcon) { + mSelIcon->refreshAspectScale(); + } + + if (mSelIcon2) { + mSelIcon2->refreshAspectScale(); + } + #endif } #endif @@ -3871,9 +3856,7 @@ void dFile_select_c::_draw() { #if PLATFORM_GCN #if TARGET_PC - mpFadePict->draw(0, 0, - mDoGph_gInf_c::getWidth(), mDoGph_gInf_c::getHeight(), false, false, - false); + dComIfGd_set2DOpaTop(&mFadeDlst); #else mpFadePict->draw(mDoGph_gInf_c::getMinXF(), mDoGph_gInf_c::getMinYF(), mDoGph_gInf_c::getWidthF(), mDoGph_gInf_c::getHeightF(), false, false, @@ -3923,6 +3906,13 @@ void dDlst_FileSel3m_c::draw() { Scr3m->draw(0.0f, 0.0f, graf); } +#ifdef TARGET_PC +void dDlst_FileSelFade_c::draw() { + mpPict->draw(mDoGph_gInf_c::getMinXF(), mDoGph_gInf_c::getMinYF(), + mDoGph_gInf_c::getWidthF(), mDoGph_gInf_c::getHeightF(), false, false, false); +} +#endif + void dFile_select_c::errorMoveAnmInitSet(int param_1, int param_2) { mErrorMsgPane->setAnimation(field_0x0090); field_0x0130 = param_1; @@ -4765,7 +4755,7 @@ void dFile_select_c::MemCardFormatYesSel2Disp() { bool isErrorTxtChange = errorTxtChangeAnm(); bool isYnMenuMove = yesnoMenuMoveAnm(); if (isErrorTxtChange == true && isYnMenuMove == true) { - mWaitTimer = g_fsHIO.card_wait_frames; + mWaitTimer = IF_DUSK(dusk::getSettings().game.instantSaves ? 0 :) g_fsHIO.card_wait_frames; mDoMemCd_Format(); mCardCheckProc = MEMCARDCHECKPROC_FORMAT; } @@ -4836,7 +4826,7 @@ void dFile_select_c::MemCardMakeGameFileSelDisp() { if (isErrorTxtChange == true && isYnMenuMove == true && isKetteiTxtDisp == true) { if (field_0x0268 != 0) { - mWaitTimer = g_fsHIO.card_wait_frames; + mWaitTimer = IF_DUSK(dusk::getSettings().game.instantSaves ? 0 :) g_fsHIO.card_wait_frames; setInitSaveData(); dataSave(); mCardCheckProc = MEMCARDCHECKPROC_MAKE_GAMEFILE; @@ -5584,7 +5574,13 @@ void dFile_select3D_c::createMirrorModel() { void dFile_select3D_c::toItem3Dpos(f32 param_0, f32 param_1, f32 param_2, cXyz* param_3) { Mtx adStack_98; Mtx auStack_c8; +#if TARGET_PC + param_0 = + (2.0f * ((param_0 - mDoGph_gInf_c::getSafeMinXF()) / mDoGph_gInf_c::getSafeWidthF()) - + 1.0f); +#else param_0 = (2.0f * ((param_0 - mDoGph_gInf_c::getMinXF()) / mDoGph_gInf_c::getWidthF()) - 1.0f); +#endif param_1 = (2.0f * ((param_1 - -100.0f) / FB_HEIGHT_BASE) - 1.0f); calcViewMtx(adStack_98); cMtx_inverse(adStack_98, auStack_c8); @@ -5601,4 +5597,3 @@ void dFile_select3D_c::calcViewMtx(Mtx param_0) { cXyz pos2(0.0f, 1.0f, 0.0f); cMtx_lookAt(param_0, &pos1, &cXyz::Zero, &pos2, 0); } - diff --git a/src/d/d_gameover.cpp b/src/d/d_gameover.cpp index baec970a32..870fb00f14 100644 --- a/src/d/d_gameover.cpp +++ b/src/d/d_gameover.cpp @@ -37,11 +37,10 @@ void dDlst_Gameover_CAPTURE_c::draw() { TGXTexObj tex_obj; Mtx44 m; -#if TARGET_PC - GXSetTexCopySrc(0, 0, mDoGph_gInf_c::getWidth(), mDoGph_gInf_c::getHeight()); - GXSetTexCopyDst(mDoGph_gInf_c::getWidth(), mDoGph_gInf_c::getHeight(), GX_TF_RGB565, GX_TRUE); -#else GXSetTexCopySrc(0, 0, FB_WIDTH, FB_HEIGHT); +#if TARGET_PC + GXSetTexCopyDst(FB_WIDTH, FB_HEIGHT, GX_TF_RGB565, GX_FALSE); +#else GXSetTexCopyDst(FB_WIDTH / 2, FB_HEIGHT / 2, GX_TF_RGB565, GX_TRUE); #endif GXCopyTex(mDoGph_gInf_c::mZbufferTex, 0); diff --git a/src/d/d_insect.cpp b/src/d/d_insect.cpp index 380f545bd3..3f1d2fae11 100644 --- a/src/d/d_insect.cpp +++ b/src/d/d_insect.cpp @@ -82,11 +82,7 @@ void dInsect_c::CalcZBuffer(f32 param_0) { pos = current.pos; pos.y += 20.0f; - #if TARGET_PC - mDoLib_project(&pos, &pos_projected, {0, 0, FB_WIDTH, FB_HEIGHT}); - #else mDoLib_project(&pos, &pos_projected); - #endif if (dComIfGp_getCamera(0)) { camera_trim_height = dComIfGp_getCamera(0)->mCamera.mTrimHeight; diff --git a/src/d/d_kankyo.cpp b/src/d/d_kankyo.cpp index 60e4d619a4..6b84fbae43 100644 --- a/src/d/d_kankyo.cpp +++ b/src/d/d_kankyo.cpp @@ -33,7 +33,9 @@ #include #include #if TARGET_PC +#include "dusk/imgui/ImGuiBloomWindow.hpp" #include "dusk/settings.h" +#include "dusk/frame_interpolation.h" #endif static void GxXFog_set(); @@ -2578,6 +2580,10 @@ void dScnKy_env_light_c::setLight() { mDoGph_gInf_c::getBloom()->setMode(mode); } +#if TARGET_PC + dusk::ApplyBloomOverride(); +#endif + f32 var_f30; if (dKy_Outdoor_check() == true) { static f32 now_shadow_alpha[] = {0.25f, 0.35f, 0.6f, 0.6f, 0.25f, 0.35f}; @@ -8246,6 +8252,10 @@ static int dKy_Create(void* i_this) { kankyo_class* kankyo = (kankyo_class*)i_this; BOOL next_time_set = false; +#if TARGET_PC + kankyo->base.draw_interp_frame = true; +#endif + stage_envr_info_class* stage_envr_p = dComIfGp_getStageEnvrInfo(); if (stage_envr_p != NULL && dComIfGp_getStartStageRoomNo() != -1) { stage_envr_p += dComIfGp_getStartStageRoomNo(); @@ -10999,11 +11009,7 @@ void dKy_depth_dist_set(void* process_p) { f32 var_f31 = sp24.abs(camera_p->view.lookat.eye); if (var_f31 < 2000.0f && var_f31 < kankyo->field_0x1268) { - #if TARGET_PC - mDoLib_project(&actor_p->eyePos, &sp30, {0, 0, FB_WIDTH, FB_HEIGHT}); - #else mDoLib_project(&actor_p->eyePos, &sp30); - #endif if ((sp30.x >= 0.0f && sp30.x < FB_WIDTH) && (sp30.y >= 0.0f && #if DEBUG diff --git a/src/d/d_kankyo_debug.cpp b/src/d/d_kankyo_debug.cpp index 03bfeac196..7fdad26ed2 100644 --- a/src/d/d_kankyo_debug.cpp +++ b/src/d/d_kankyo_debug.cpp @@ -915,12 +915,7 @@ void dKydb_dungeonlight_draw() { rot.y = 0; dDbVw_drawCubeXlu(player->current.pos, size, rot, color); - #if TARGET_PC - mDoLib_project(&g_env_light.dungeonlight[i].mPosition, &proj, {0, 0, FB_WIDTH, FB_HEIGHT}); - #else mDoLib_project(&g_env_light.dungeonlight[i].mPosition, &proj); - #endif - if (proj.x > 30.0f) { proj.x -= 30.0f; } diff --git a/src/d/d_kankyo_rain.cpp b/src/d/d_kankyo_rain.cpp index 953768a525..64cf2247e0 100644 --- a/src/d/d_kankyo_rain.cpp +++ b/src/d/d_kankyo_rain.cpp @@ -116,12 +116,7 @@ void dKyr_lenzflare_move() { cXyz vect; cXyz proj; cXyz center; - - #if TARGET_PC - mDoLib_project(lenz_packet->mPositions, &proj, {0, 0, FB_WIDTH, FB_HEIGHT}); - #else mDoLib_project(lenz_packet->mPositions, &proj); - #endif center.x = FB_WIDTH / 2; center.y = FB_HEIGHT / 2; @@ -221,12 +216,7 @@ void dKyr_sun_move() { } cXyz proj; - - #if TARGET_PC - mDoLib_project(sun_packet->mPos, &proj, {0, 0, FB_WIDTH, FB_HEIGHT}); - #else mDoLib_project(sun_packet->mPos, &proj); - #endif for (int i = 0; i < 5; i++) { cXyz chkpnt = proj; @@ -4110,62 +4100,24 @@ void dKyr_drawStar(Mtx drawMtx, u8** tex) { color_reg0.b = 0xFF; color_reg0.a = 0xFF; -#if TARGET_PC - Mtx star_gx_view; - cXyz anchor_eye; - f32 star_fovy = 45.0f; - MtxP gx_load_view = drawMtx; - bool star_use_present_view = false; - - if (dusk::getSettings().game.enableFrameInterpolation) { - star_use_present_view = dusk::frame_interp::build_star_view(star_gx_view, camMtx, &anchor_eye, &star_fovy); - } - - if (star_use_present_view) { - gx_load_view = star_gx_view; - } else { - if (dComIfGd_getView() != NULL) { - MTXInverse(dComIfGd_getView()->viewMtxNoTrans, camMtx); - anchor_eye = camera->view.lookat.eye; - star_fovy = dComIfGd_getView()->fovy; - } else { - return; - } - } -#else if (dComIfGd_getView() != NULL) { MTXInverse(dComIfGd_getView()->viewMtxNoTrans, camMtx); } else { return; } -#endif if (strcmp(dComIfGp_getStartStageName(), "F_SP200") == 0 && dComIfG_play_c::getLayerNo(0) == 0) { moon_pos = envlight->moon_pos; } else { -#if TARGET_PC - moon_pos = anchor_eye + envlight->moon_pos; -#else moon_pos = camera->view.lookat.eye + envlight->moon_pos; -#endif if (sp38) { -#if TARGET_PC - moon_pos.x = 3900.0f + anchor_eye.x; - moon_pos.y = 8052.0f + anchor_eye.y; - moon_pos.z = -9072.0f + anchor_eye.z; -#else moon_pos.x = 3900.0f + camera->view.lookat.eye.x; moon_pos.y = 8052.0f + camera->view.lookat.eye.y; moon_pos.z = -9072.0f + camera->view.lookat.eye.z; -#endif } } -#if TARGET_PC - mDoLib_project(&moon_pos, &moon_proj, {0, 0, FB_WIDTH, FB_HEIGHT}); -#else mDoLib_project(&moon_pos, &moon_proj); -#endif // Dusk optimization: we use vertex color rather than GX_TEVREG0 to set star color. // This allows us to merge all the stars into a single draw. @@ -4193,11 +4145,7 @@ void dKyr_drawStar(Mtx drawMtx, u8** tex) { MTXRotRad(rotMtx, 'Z', DEG_TO_RAD(rot)); MTXConcat(camMtx, rotMtx, camMtx); -#if TARGET_PC - GXLoadPosMtxImm(gx_load_view, GX_PNMTX0); -#else GXLoadPosMtxImm(drawMtx, GX_PNMTX0); -#endif GXSetCurrentMtx(GX_PNMTX0); rot += 0.65f; @@ -4205,23 +4153,12 @@ void dKyr_drawStar(Mtx drawMtx, u8** tex) { rot = 0.0f; } -#if TARGET_PC - spBC = anchor_eye; -#else spBC.x = camera->view.lookat.eye.x; spBC.y = camera->view.lookat.eye.y; spBC.z = camera->view.lookat.eye.z; -#endif f32 sp34 = -1.0f; int sp30 = 0; -#if TARGET_PC - f32 var_f30 = star_fovy / 45.0f; - if (var_f30 >= 1.0f) { - var_f30 = 1.0f; - } - var_f30 = 1.0f - var_f30; -#else f32 var_f30 = 0.0f; if (dComIfGd_getView() != NULL) { @@ -4231,7 +4168,6 @@ void dKyr_drawStar(Mtx drawMtx, u8** tex) { } var_f30 = 1.0f - var_f30; } -#endif f32 temp_f27 = 0.28f * (1.0f - var_f30); sp98.x = 0.0f; @@ -4330,11 +4266,7 @@ void dKyr_drawStar(Mtx drawMtx, u8** tex) { sp68.y = spBC.y + star_pos.y; sp68.z = spBC.z + star_pos.z; - #if TARGET_PC - mDoLib_project(&sp68, &star_proj, {0, 0, FB_WIDTH, FB_HEIGHT}); - #else mDoLib_project(&sp68, &star_proj); - #endif moon_proj.z = 0.0f; star_proj.z = 0.0f; @@ -4749,11 +4681,7 @@ void drawVrkumo(Mtx drawMtx, GXColor& color, u8** tex) { } if (g_env_light.daytime > 105.0f && g_env_light.daytime < 240.0f && !dComIfGp_event_runCheck() && sun_packet != NULL && sun_packet->mSunAlpha > 0.0f) { - #if TARGET_PC - mDoLib_project(&sun_packet->mPos[0], &proj, {0, 0, FB_WIDTH, FB_HEIGHT}); - #else mDoLib_project(&sun_packet->mPos[0], &proj); - #endif if (proj.x > 0.0f && proj.x < FB_WIDTH && proj.y > spC4 && proj.y < (458.0f - spC4)) { pass = 0; } @@ -5018,12 +4946,7 @@ void drawVrkumo(Mtx drawMtx, GXColor& color, u8** tex) { x = 100.0f; y = 100.0f; z = 100.0f; - - #if TARGET_PC - mDoLib_project(&spF0, &proj, {0, 0, FB_WIDTH, FB_HEIGHT}); - #else mDoLib_project(&spF0, &proj); - #endif if (proj.x > -x && proj.x < (FB_WIDTH + x) && proj.y > -y && proj.y < (458.0f + z)) { break; @@ -5073,12 +4996,7 @@ void drawVrkumo(Mtx drawMtx, GXColor& color, u8** tex) { x = 100.0f; y = 100.0f; z = 100.0f; - - #if TARGET_PC - mDoLib_project(&spE4, &proj, {0, 0, FB_WIDTH, FB_HEIGHT}); - #else mDoLib_project(&spE4, &proj); - #endif if (proj.x > -x && proj.x < (FB_WIDTH + x) && proj.y > -y && proj.y < (458.0f + z)) { break; @@ -6118,13 +6036,7 @@ static void dKyr_evil_draw2(Mtx drawMtx, u8** tex) { sp34.x = 80.0f; sp34.y = 80.0f; sp34.z = 80.0f; - - #if TARGET_PC - mDoLib_project(&sp7C, &proj, {0, 0, FB_WIDTH, FB_HEIGHT}); - #else mDoLib_project(&sp7C, &proj); - #endif - if (!(proj.x > -sp34.x) || !(proj.x < (FB_WIDTH + sp34.x)) || !(proj.y > -sp34.y) || !(proj.y < (458.0f + sp34.z))) { @@ -6349,12 +6261,7 @@ void dKyr_evil_draw(Mtx drawMtx, u8** tex) { sp44.y = 80.0f; sp44.z = 80.0f; - #if TARGET_PC - mDoLib_project(&spA4, &proj, {0, 0, FB_WIDTH, FB_HEIGHT}); - #else mDoLib_project(&spA4, &proj); - #endif - if (!(proj.x > -sp44.x) || !(proj.x < (FB_WIDTH + sp44.x)) || !(proj.y > -sp44.y) || !(proj.y < (458.0f + sp44.z))) { diff --git a/src/d/d_map_path.cpp b/src/d/d_map_path.cpp index f7ce0f1a51..916bd2c223 100644 --- a/src/d/d_map_path.cpp +++ b/src/d/d_map_path.cpp @@ -16,10 +16,6 @@ #ifdef TARGET_PC constexpr u16 kMapResolutionMultiplier = 4; -// Line widths are relative to the framebuffer size. Since we're rendering to a separate -// framebuffer, we have to scale them accordingly. The original game used about half of the -// EFB for the map rendering, so this is a reasonable approximation. -constexpr u8 kMapLineWidthMultiplier = 2; #endif void dMpath_n::dTexObjAggregate_c::create() { @@ -242,11 +238,7 @@ void dDrawPath_c::rendering(dDrawPath_c::line_class const* p_line) { int width = getLineWidth(p_line->field_0x1); if (width > 0 && p_line->mDataNum >= 2) { -#ifdef TARGET_PC - GXSetLineWidth(width * kMapLineWidthMultiplier, GX_TO_ZERO); -#else - GXSetLineWidth(width * 2, GX_TO_ZERO); -#endif + GXSetLineWidth(width, GX_TO_ZERO); GXSetTevColor(GX_TEVREG0, *getLineColor(p_line->field_0x0 & 0x3F, p_line->field_0x1)); GXBegin(GX_LINESTRIP, GX_VTXFMT0, p_line->mDataNum); @@ -435,8 +427,12 @@ void dRenderingFDAmap_c::preRenderingMap() { const u16 w = mTexWidth * kMapResolutionMultiplier; const u16 h = mTexHeight * kMapResolutionMultiplier; GXCreateFrameBuffer(w, h); - GXSetViewport(0.0f, 0.0f, w, h, 0.0f, 1.0f); - GXSetScissor(0, 0, w, h); + // Set logical viewport dimensions + GXSetViewport(0.0f, 0.0f, mTexWidth, mTexHeight, 0.0f, 1.0f); + GXSetScissor(0, 0, mTexWidth, mTexHeight); + // Set render viewport dimensions + GXSetViewportRender(0.0f, 0.0f, w, h, 0.0f, 1.0f); + GXSetScissorRender(0, 0, w, h); #else GXSetViewport(0.0f, 0.0f, mTexWidth, mTexHeight, 0.0f, 1.0f); GXSetScissor(0, 0, mTexWidth, mTexHeight); @@ -456,6 +452,12 @@ void dRenderingFDAmap_c::preRenderingMap() { GXSetClipMode(GX_CLIP_ENABLE); setTevSettingNonTextureDirectColor(); f32 right = field_0x8 * 0.5f; +#if TARGET_PC + if (dusk::getSettings().game.enableMirrorMode) { + right = field_0x8 * -0.5f; + } +#endif + f32 top = field_0xc * 0.5f; Mtx44 matrix; C_MTXOrtho(matrix, top, -top, -right, right, 0.0f, 10000.0f); @@ -511,13 +513,8 @@ void dRenderingFDAmap_c::renderingDecoration(dDrawPath_c::line_class const* p_li BE(u16)* data_p = p_line->mpData; s32 data_num = p_line->mDataNum; -#ifdef TARGET_PC - GXSetLineWidth(width * kMapLineWidthMultiplier, GX_TO_ZERO); - GXSetPointSize(width * kMapLineWidthMultiplier, GX_TO_ONE); -#else GXSetLineWidth(width, GX_TO_ONE); GXSetPointSize(width, GX_TO_ONE); -#endif GXColor lineColor = *getDecoLineColor(p_line->field_0x0 & 0x3f, p_line->field_0x1); GXSetTevColor(GX_TEVREG0, lineColor); lineColor.r = lineColor.r - 4; diff --git a/src/d/d_menu_collect.cpp b/src/d/d_menu_collect.cpp index c7e5bb7414..46c9c1a9d9 100644 --- a/src/d/d_menu_collect.cpp +++ b/src/d/d_menu_collect.cpp @@ -99,7 +99,7 @@ dMenu_Collect2D_c::~dMenu_Collect2D_c() { void dMenu_Collect2D_c::menuCollectWide() { // Main Canvas mpScreen->scale(mDoGph_gInf_c::hudAspectScaleUp, 1.0f); - mpScreen->translate(mDoGph_gInf_c::getMinXF(), 0.0f); + mpScreen->translate(mDoGph_gInf_c::getSafeMinXF(), 0.0f); // Pieces of Heart mpScreen->search(MULTI_CHAR('heart_n'))->scale(mDoGph_gInf_c::hudAspectScaleDown, 1.0f); @@ -164,11 +164,22 @@ void dMenu_Collect2D_c::menuCollectWide() { // Item Description Text mpScreen->search(MULTI_CHAR('infotxtn'))->scale(mDoGph_gInf_c::hudAspectScaleDown, 1.0f); + + #if TARGET_PC + if (mpDrawCursor) { + mpDrawCursor->refreshAspectScale(); + } + #endif } #endif void dMenu_Collect2D_c::_create() { mpHeap->getTotalFreeSize(); + + #if TARGET_PC + mpDrawCursor = NULL; + #endif + mpScreen = JKR_NEW J2DScreen(); mpScreen->setPriority("zelda_collect_soubi_screen.blo", 0x1020000, dComIfGp_getCollectResArchive()); @@ -182,7 +193,7 @@ void dMenu_Collect2D_c::_create() { } #if TARGET_PC - mpScreenIcon->translate(-mDoGph_gInf_c::getMinXF(), 0.0f); + mpScreenIcon->translate(-mDoGph_gInf_c::getSafeMinXF(), 0.0f); #endif dPaneClass_showNullPane(mpScreenIcon); @@ -1100,23 +1111,11 @@ void dMenu_Collect2D_c::cursorPosSet() { Vec pos = mpSelPm[mCursorX][mCursorY]->getGlobalVtxCenter(false, 0); mpDrawCursor->setPos(pos.x, pos.y, mpSelPm[mCursorX][mCursorY]->getPanePtr(), false); if (mCursorY == 5) { - #if TARGET_PC - mpDrawCursor->setParam(1.1f * mDoGph_gInf_c::hudAspectScaleUp, 0.85f, 0.05f, 0.5f, 0.5f); - #else mpDrawCursor->setParam(1.1f, 0.85f, 0.05f, 0.5f, 0.5f); - #endif } else if (mCursorX == 6 && mCursorY == 0) { - #if TARGET_PC - mpDrawCursor->setParam(0.6f * mDoGph_gInf_c::hudAspectScaleUp, 0.85f, 0.03f, 0.6f, 0.6f); - #else mpDrawCursor->setParam(0.6f, 0.85f, 0.03f, 0.6f, 0.6f); - #endif } else { - #if TARGET_PC - mpDrawCursor->setParam(1.0f * mDoGph_gInf_c::hudAspectScaleUp, 1.0f, 0.1f, 0.7f, 0.7f); - #else mpDrawCursor->setParam(1.0f, 1.0f, 0.1f, 0.7f, 0.7f); - #endif } } @@ -2688,12 +2687,7 @@ u8 dMenu_Collect3D_c::getMaskMdlVisible() { f32 dMenu_Collect3D_c::mViewOffsetY = -100.0f; void dMenu_Collect3D_c::setupItem3D(Mtx param_0) { -#if TARGET_PC - f32 scaleFactor = mDoGph_gInf_c::getHeight() / FB_HEIGHT; - GXSetViewport(0.0f, mViewOffsetY * scaleFactor, mDoGph_gInf_c::getWidth(), mDoGph_gInf_c::getHeight(), 0.0f, 1.0f); -#else GXSetViewport(0.0f, mViewOffsetY, FB_WIDTH, FB_HEIGHT, 0.0f, 1.0f); -#endif mViewOffsetY = -100.0f; Mtx44 projection; C_MTXPerspective(projection, 45.0f, mDoGph_gInf_c::getAspect(), 1.0f, 100000.0f); @@ -2706,8 +2700,14 @@ void dMenu_Collect3D_c::setupItem3D(Mtx param_0) { void dMenu_Collect3D_c::toItem3Dpos(f32 param_0, f32 param_1, f32 param_2, cXyz* param_3) { Mtx adStack_98; Mtx auStack_c8; +#if TARGET_PC + param_0 = + (2.0f * ((param_0 - mDoGph_gInf_c::getSafeMinXF()) / mDoGph_gInf_c::getSafeWidthF()) - + 1.0f); +#else param_0 = (2.0f * ((param_0 - mDoGph_gInf_c::getMinXF()) / mDoGph_gInf_c::getWidthF()) - 1.0f); +#endif param_1 = (2.0f * ((param_1 - -100.0f) / FB_HEIGHT_BASE) - 1.0f); calcViewMtx(adStack_98); MTXInverse(adStack_98, auStack_c8); diff --git a/src/d/d_menu_dmap.cpp b/src/d/d_menu_dmap.cpp index 739dfc7843..17d9193241 100644 --- a/src/d/d_menu_dmap.cpp +++ b/src/d/d_menu_dmap.cpp @@ -864,33 +864,15 @@ void dMenu_DmapBg_c::draw() { J2DOrthoGraph* grafContext = (J2DOrthoGraph*)dComIfGp_getCurrentGrafPort(); grafContext->setup2D(); -#if TARGET_PC - // GXGetScissor uses 11-bit GC register fields (max 2047) which overflow - // at window widths > ~1705px, producing garbage values on restore. - scissor_left = 0; - scissor_top = 0; - scissor_width = (u32)mDoGph_gInf_c::getWidth(); - scissor_height = (u32)mDoGph_gInf_c::getHeight(); -#else GXGetScissor(&scissor_left, &scissor_top, &scissor_width, &scissor_height); -#endif -#if TARGET_PC - grafContext->scissor(field_0xd94, 0.0f, mDoGph_gInf_c::getWidth(), mDoGph_gInf_c::getHeight()); -#else - grafContext->scissor(field_0xd94, 0.0f, FB_WIDTH, FB_HEIGHT); -#endif + grafContext->scissor(field_0xd94, 0.0f, FB_WIDTH, FB_HEIGHT); grafContext->setScissor(); mBaseScreen->draw(field_0xd94, field_0xd98, grafContext); dMenu_Dmap_c::myclass->drawFloorScreenBack(mFloorScreen, field_0xd94, field_0xd98, grafContext); -#if TARGET_PC - f32 dVar21 = mDoGph_gInf_c::getWidthF() / mDoGph_gInf_c::getWidth(); - f32 dVar16 = mDoGph_gInf_c::getHeightF() / mDoGph_gInf_c::getHeight(); -#else f32 dVar21 = mDoGph_gInf_c::getWidthF() / FB_WIDTH; f32 dVar16 = mDoGph_gInf_c::getHeightF() / FB_HEIGHT; -#endif mMapScreen[0]->draw(field_0xd94, field_0xd98, grafContext); if (mpBackTexture != NULL) { @@ -922,15 +904,9 @@ void dMenu_DmapBg_c::draw() { mpBackTexture->draw(local_28c, field_0xd94 + mpBackTexture->getBounds().i.y, mpBackTexture->getWidth(), mpBackTexture->getHeight(), false, false, false); -#if TARGET_PC - grafContext->scissor(field_0xd94, - 0, mDoGph_gInf_c::getWidth(), - scissor_height); -#else grafContext->scissor(field_0xd94 + mDoGph_gInf_c::getMinXF(), scissor_top, mDoGph_gInf_c::getWidthF(), scissor_height); -#endif grafContext->setScissor(); } @@ -957,11 +933,7 @@ void dMenu_DmapBg_c::draw() { Vec local_26c = pane.getGlobalVtx(mMapPane, &local_110, 0, false, 0); drawIcon(local_26c.x + field_0xd94, local_26c.y, field_0xda8, 1.0f); -#if TARGET_PC - grafContext->scissor(field_0xd94, scissor_top, mDoGph_gInf_c::getWidth(), scissor_height); -#else grafContext->scissor(field_0xd94 + mDoGph_gInf_c::getMinXF(), scissor_top, mDoGph_gInf_c::getWidthF(), scissor_height); -#endif grafContext->setScissor(); grafContext->scissor(scissor_left, scissor_top, scissor_width, scissor_height); grafContext->setScissor(); @@ -1012,7 +984,36 @@ void dMenu_DmapBg_c::update() { JUT_ASSERT(2323, mpBackTexture != NULL); void* spec = mpArchive->getResource("spec/spec.dat"); + #if TARGET_PC + struct dmap_spec { + /* 0x00 */ BE(f32) field_0x0; + /* 0x04 */ BE(f32) field_0x4; + /* 0x08 */ BE(f32) field_0x8; + /* 0x0C */ u8 field_0xc; + /* 0x0D */ u8 field_0xd; + /* 0x0E */ u8 field_0xe; + /* 0x0F */ u8 field_0xf; + /* 0x10 */ u8 field_0x10; + /* 0x11 */ u8 field_0x11; + /* 0x12 */ u8 field_0x12; + /* 0x13 */ u8 field_0x13; + }; + dmap_spec* dspec = (dmap_spec*)spec; + + field_0xd80 = dspec->field_0x0; + field_0xd84 = dspec->field_0x4; + field_0xd88 = dspec->field_0x8; + field_0xd8c = dspec->field_0xc; + field_0xd8d = dspec->field_0xd; + field_0xd8e = dspec->field_0xe; + field_0xd8f = dspec->field_0xf; + field_0xd90 = dspec->field_0x10; + field_0xd91 = dspec->field_0x11; + field_0xd92 = dspec->field_0x12; + field_0xd93 = dspec->field_0x13; + #else memcpy(&field_0xd80, spec, 20); + #endif } } diff --git a/src/d/d_menu_fmap2D.cpp b/src/d/d_menu_fmap2D.cpp index 558ad05d6d..8692156daf 100644 --- a/src/d/d_menu_fmap2D.cpp +++ b/src/d/d_menu_fmap2D.cpp @@ -276,18 +276,14 @@ void dMenu_Fmap2DBack_c::draw() { u32 scissorLeft, scissorTop, scissorWidth, scissorHeight; GXGetScissor(&scissorLeft, &scissorTop, &scissorWidth, &scissorHeight); -#if TARGET_PC - grafPort->scissor(mTransX, 0.0f, mDoGph_gInf_c::getWidth(), mDoGph_gInf_c::getHeight()); -#else grafPort->scissor(mTransX, 0.0f, FB_WIDTH, FB_HEIGHT); -#endif grafPort->setScissor(); mpBackTex->setBlackWhite(field_0x1208, field_0x120c); mpBackTex->setAlpha(mAlphaRate * 255.0f * g_fmapHIO.mBackgroundAlpha); mpBackTex->draw(mTransX + mDoGph_gInf_c::getMinXF(), - mTransZ + mDoGph_gInf_c::getMinYF(), mDoGph_gInf_c::getWidth(), - mDoGph_gInf_c::getHeight(), false, false, false); + mTransZ + mDoGph_gInf_c::getMinYF(), mDoGph_gInf_c::getWidthF(), + mDoGph_gInf_c::getHeightF(), false, false, false); mpBackScreen->draw(mTransX, mTransZ, grafPort); mpBaseScreen->draw(mTransX, mTransZ, grafPort); @@ -297,13 +293,8 @@ void dMenu_Fmap2DBack_c::draw() { Vec vec2 = mpMapArea->getGlobalVtx(&mtx, 3, false, 0); -#if TARGET_PC - f32 width = mDoGph_gInf_c::getWidthF() / mDoGph_gInf_c::getWidth(); - f32 height = mDoGph_gInf_c::getHeightF() / mDoGph_gInf_c::getHeight(); -#else f32 width = mDoGph_gInf_c::getWidthF() / FB_WIDTH; f32 height = mDoGph_gInf_c::getHeightF() / FB_HEIGHT; -#endif grafPort->scissor(mTransX + ((vec1.x - mDoGph_gInf_c::getMinXF()) / width), mTransZ + (vec1.y / height), (vec2.x - vec1.x) / width, @@ -351,8 +342,13 @@ void dMenu_Fmap2DBack_c::draw() { scrollAreaDraw(); } - blinkMove(30); - moveLightDropAnime(); +#ifdef TARGET_PC + if (dusk::frame_interp::get_ui_tick_pending()) +#endif + { + blinkMove(30); + moveLightDropAnime(); + } setCenterPosX(field_0x11dc, 1); drawIcon(mTransX, mTransZ, mAlphaRate, field_0xfa8 * mSpotTextureFadeAlpha); @@ -360,11 +356,7 @@ void dMenu_Fmap2DBack_c::draw() { drawDebugRegionArea(); } -#if TARGET_PC - grafPort->scissor(scissorLeft, scissorTop, mDoGph_gInf_c::getWidth(), mDoGph_gInf_c::getHeight()); -#else grafPort->scissor(scissorLeft, scissorTop, scissorWidth, scissorHeight); -#endif grafPort->setScissor(); if (isArrowDrawFlag()) { @@ -392,16 +384,15 @@ void dMenu_Fmap2DBack_c::draw() { &mArrowPos2DY); #ifdef TARGET_PC - for (u32 i = 0; i < dusk::frame_interp::get_presentation_ui_advance_ticks(); ++i) { + if (dusk::frame_interp::get_ui_tick_pending()) #endif + { field_0x11e0 -= g_fmapHIO.mCursorSpeed; if (field_0x11e0 < 0.0f) { field_0x11e0 += 360.0f; } -#ifdef TARGET_PC } -#endif mpPointParent->getPanePtr()->rotate(mpPointParent->getSizeX() / 2.0f, mpPointParent->getSizeY() / 2.0f, ROTATE_Z, @@ -1778,14 +1769,19 @@ void dMenu_Fmap2DBack_c::calcBlink() { t * (g_fmapHIO.mMapBlink[i + 1].mUnselectedRegion.mBlinkSpeed - g_fmapHIO.mMapBlink[i].mUnselectedRegion.mBlinkSpeed); - field_0x1218++; - if (field_0x1218 >= selected_blink_speed) { - field_0x1218 = 0; - } +#if TARGET_PC + if (dusk::frame_interp::get_ui_tick_pending()) +#endif + { + field_0x1218++; + if (field_0x1218 >= selected_blink_speed) { + field_0x1218 = 0; + } - field_0x121a++; - if (field_0x121a >= unselected_blink_speed) { - field_0x121a = 0; + field_0x121a++; + if (field_0x121a >= unselected_blink_speed) { + field_0x121a = 0; + } } f32 t_selected = 0.0f; @@ -2580,11 +2576,7 @@ void dMenu_Fmap2DTop_c::draw() { J2DOrthoGraph* ctx = static_cast(dComIfGp_getCurrentGrafPort()); ctx->setup2D(); GXGetScissor(&scissor_left, &scissor_top, &scissor_width, &scissor_height); -#if TARGET_PC - ctx->scissor(mTransX, 0.0f, mDoGph_gInf_c::getWidth(), mDoGph_gInf_c::getHeight()); -#else ctx->scissor(mTransX, 0.0f, FB_WIDTH, FB_HEIGHT); -#endif ctx->setScissor(); mpTitleScreen->draw(mTransX, mTransY, ctx); ctx->scissor(scissor_left, scissor_top, scissor_width, scissor_height); diff --git a/src/d/d_menu_letter.cpp b/src/d/d_menu_letter.cpp index cef018372f..a0155ef6b4 100644 --- a/src/d/d_menu_letter.cpp +++ b/src/d/d_menu_letter.cpp @@ -223,13 +223,8 @@ void dMenu_Letter_c::_draw() { f32 y1 = local_178.y; Vec local_184; local_184 = afStack_138.getGlobalVtx(field_0x1ec, &mtx, 3, false, 0); -#if TARGET_PC - f32 dVar17 = mDoGph_gInf_c::getWidthF() / mDoGph_gInf_c::getWidth(); - f32 dVar16 = mDoGph_gInf_c::getHeightF() / mDoGph_gInf_c::getHeight(); -#else f32 dVar17 = mDoGph_gInf_c::getWidthF() / FB_WIDTH; f32 dVar16 = mDoGph_gInf_c::getHeightF() / FB_HEIGHT; -#endif f32 fVar1 = (x1 - mDoGph_gInf_c::getMinXF()) / dVar17; f32 fVar2 = y1 / dVar16; grafContext->scissor(fVar1, fVar2, diff --git a/src/d/d_menu_option.cpp b/src/d/d_menu_option.cpp index 9384bc06b9..af322a1c01 100644 --- a/src/d/d_menu_option.cpp +++ b/src/d/d_menu_option.cpp @@ -555,19 +555,23 @@ void dMenu_Option_c::_draw() { #endif mpBlackTex->setAlpha(0xff); + #if TARGET_PC mpBlackTex->draw(mDoGph_gInf_c::getMinXF(), mDoGph_gInf_c::getMinYF(), mDoGph_gInf_c::getWidthF(), mDoGph_gInf_c::getHeightF(), 0, 0, 0); #else mpBlackTex->draw(0.0f, 0.0f, FB_WIDTH, FB_HEIGHT, 0, 0, 0); #endif + mpBackScreen->draw(0.0f, 0.0f, ctx); f32 alpha = (f32)g_drawHIO.mOptionScreen.mBackgroundAlpha * (f32)field_0x374; mpBlackTex->setAlpha(alpha); + #if TARGET_PC mpBlackTex->draw(mDoGph_gInf_c::getMinXF(), mDoGph_gInf_c::getMinYF(), mDoGph_gInf_c::getWidthF(), mDoGph_gInf_c::getHeightF(), 0, 0, 0); #else mpBlackTex->draw(0.0f, 0.0f, FB_WIDTH, FB_HEIGHT, 0, 0, 0); #endif + mpScreen->draw(0.0f, 0.0f, ctx); mpClipScreen->draw(0.0f, 0.0f, ctx); #if TARGET_PC diff --git a/src/d/d_menu_ring.cpp b/src/d/d_menu_ring.cpp index 8e5b3cacd2..529597248a 100644 --- a/src/d/d_menu_ring.cpp +++ b/src/d/d_menu_ring.cpp @@ -29,6 +29,10 @@ #include +#if TARGET_PC +#include "dusk/game_clock.h" +#endif + typedef void (dMenu_Ring_c::*initFunc)(); static initFunc stick_init[] = { /* STATUS_WAIT */ &dMenu_Ring_c::stick_wait_init, @@ -183,6 +187,9 @@ dMenu_Ring_c::dMenu_Ring_c(JKRExpHeap* i_heap, STControl* i_stick, CSTControl* i } for (int i = 0; i < 4; i++) { field_0x674[i] = 0; +#if TARGET_PC + mSelectItemSlideElapsed[i] = 0.0f; +#endif field_0x518[i] = 0.0f; field_0x528[i] = 0.0f; field_0x538[i] = 0.0f; @@ -1022,6 +1029,9 @@ void dMenu_Ring_c::setJumpItem(bool i_useVibrationM) { field_0x6b8[0] != dComIfGs_getMixItemIndex(0)) { field_0x674[0] = 1; +#if TARGET_PC + mSelectItemSlideElapsed[0] = 0.0f; +#endif } } else if (field_0x6b3 == 1) { field_0x538[0] = g_ringHIO.mUnselectItemScale; @@ -1030,6 +1040,9 @@ void dMenu_Ring_c::setJumpItem(bool i_useVibrationM) { field_0x6b8[1] != dComIfGs_getMixItemIndex(1)) { field_0x674[1] = 1; +#if TARGET_PC + mSelectItemSlideElapsed[1] = 0.0f; +#endif } } if (field_0x674[0] == 1) { @@ -1520,7 +1533,15 @@ void dMenu_Ring_c::setSelectItem(int i_idx, u8 i_itemNo) { void dMenu_Ring_c::drawSelectItem() { for (int i = 0; i < 4; i++) { if (field_0x674[i] != 0) { +#if TARGET_PC + mSelectItemSlideElapsed[i] += dusk::game_clock::consume_interval(this); + const f32 u = std::min(mSelectItemSlideElapsed[i] / dusk::game_clock::period_for_original_frames(10.0f), 1.0f); + if (u >= 1.0f) { + setSelectItemForce(i); + } else { +#else if (field_0x674[i] < 10) { +#endif f32 initSizeX = dMeter2Info_getMeterItemPanePtr(i)->getInitSizeX() * 1.7f; f32 initSizeY = dMeter2Info_getMeterItemPanePtr(i)->getInitSizeY() * 1.7f; f32 initScaleX = dMeter2Info_getMeterItemPanePtr(i)->getInitScaleX(); @@ -1528,7 +1549,11 @@ void dMenu_Ring_c::drawSelectItem() { Vec pos = dMeter2Info_getMeterItemPanePtr(i)->getGlobalVtxCenter( dMeter2Info_getMeterItemPanePtr(i)->mPane, true, 0); +#if TARGET_PC + f32 fVar14 = 0.1f + 0.8f * u; +#else f32 fVar14 = field_0x674[i] / 10.0f; +#endif if (field_0x6cd != 0xff) { fVar14 = 1.0f - fVar14; } @@ -1549,9 +1574,11 @@ void dMenu_Ring_c::drawSelectItem() { 0); } } +#if !TARGET_PC field_0x674[i]++; } else { setSelectItemForce(i); +#endif } } } @@ -1562,6 +1589,9 @@ void dMenu_Ring_c::setSelectItemForce(int i_idx) { if (field_0x674[i_idx] != 0) { dComIfGs_setSelectItemIndex(i_idx, field_0x6b4[i_idx]); field_0x674[i_idx] = 0; +#if TARGET_PC + mSelectItemSlideElapsed[i_idx] = 0.0f; +#endif } } else if (field_0x674[i_idx] != 0) { for (int i = 0; i < 2; i++) { @@ -1569,6 +1599,9 @@ void dMenu_Ring_c::setSelectItemForce(int i_idx) { dComIfGs_setSelectItemIndex(i, field_0x6b4[i]); } field_0x674[i_idx] = 0; +#if TARGET_PC + mSelectItemSlideElapsed[i_idx] = 0.0f; +#endif } } diff --git a/src/d/d_menu_save.cpp b/src/d/d_menu_save.cpp index 618677866b..53dbe37ca4 100644 --- a/src/d/d_menu_save.cpp +++ b/src/d/d_menu_save.cpp @@ -18,6 +18,7 @@ #include "m_Do/m_Do_controller_pad.h" #include "m_Do/m_Do_graphic.h" #include "d/d_msg_scrn_explain.h" +#include "dusk/frame_interpolation.h" #include "dusk/settings.h" #include "JSystem/J2DGraph/J2DAnmLoader.h" #include "f_op/f_op_msg_mng.h" @@ -386,11 +387,7 @@ void dMenu_save_c::screenSet() { mSelectedFile = dComIfGs_getDataNum(); mSelIcon = JKR_NEW dSelect_cursor_c(0, 1.0f, NULL); - #if TARGET_PC - mSelIcon->setParam(0.96f * mDoGph_gInf_c::hudAspectScaleUp, 0.94f, 0.03f, 0.7f, 0.7f); - #else mSelIcon->setParam(0.96f, 0.94f, 0.03f, 0.7f, 0.7f); - #endif Vec pos; pos = mpSelData[mSelectedFile]->getGlobalVtxCenter(false, 0); @@ -719,7 +716,9 @@ void dMenu_save_c::_move() { } (this->*MenuSaveProc[mMenuProc])(); +#if !TARGET_PC saveSelAnm(); +#endif if (mWarning != NULL) { mWarning->_move(); @@ -736,36 +735,46 @@ void dMenu_save_c::saveSelAnm() { } void dMenu_save_c::selFileWakuAnm() { - mFileWakuAnmFrame += 2; - if (mFileWakuAnmFrame >= mpFileWakuAnm->getFrameMax()) { - mFileWakuAnmFrame -= mpFileWakuAnm->getFrameMax(); +#if TARGET_PC + if (dusk::frame_interp::get_ui_tick_pending()) +#endif + { + mFileWakuAnmFrame += 2; + if (mFileWakuAnmFrame >= mpFileWakuAnm->getFrameMax()) { + mFileWakuAnmFrame -= mpFileWakuAnm->getFrameMax(); + } + + mFileWakuRotAnmFrame += 2; + if (mFileWakuRotAnmFrame >= mpFileWakuRotAnm->getFrameMax()) { + mFileWakuRotAnmFrame -= mpFileWakuRotAnm->getFrameMax(); + } } mpFileWakuAnm->setFrame(mFileWakuAnmFrame); - - mFileWakuRotAnmFrame += 2; - if (mFileWakuRotAnmFrame >= mpFileWakuRotAnm->getFrameMax()) { - mFileWakuRotAnmFrame -= mpFileWakuRotAnm->getFrameMax(); - } mpFileWakuRotAnm->setFrame(mFileWakuRotAnmFrame); } void dMenu_save_c::bookIconAnm() { - field_0x154 += 2; - if (field_0x154 >= field_0x150->getFrameMax()) { - field_0x154 -= field_0x150->getFrameMax(); +#if TARGET_PC + if (dusk::frame_interp::get_ui_tick_pending()) +#endif + { + field_0x154 += 2; + if (field_0x154 >= field_0x150->getFrameMax()) { + field_0x154 -= field_0x150->getFrameMax(); + } + + field_0x15c += 2; + if (field_0x15c >= field_0x158->getFrameMax()) { + field_0x15c -= field_0x158->getFrameMax(); + } + + field_0x164 += 2; + if (field_0x164 >= field_0x160->getFrameMax()) { + field_0x164 -= field_0x160->getFrameMax(); + } } field_0x150->setFrame(field_0x154); - - field_0x15c += 2; - if (field_0x15c >= field_0x158->getFrameMax()) { - field_0x15c -= field_0x158->getFrameMax(); - } field_0x158->setFrame(field_0x15c); - - field_0x164 += 2; - if (field_0x164 >= field_0x160->getFrameMax()) { - field_0x164 -= field_0x160->getFrameMax(); - } field_0x160->setFrame(field_0x164); } @@ -2523,11 +2532,7 @@ void dMenu_save_c::yesnoCursorShow() { mSelIcon->setPos(pos.x, pos.y, mpNoYes[mYesNoCursor]->getPanePtr(), true); mSelIcon->setAlphaRate(1.0f); - #if TARGET_PC - mSelIcon->setParam(0.96f * mDoGph_gInf_c::hudAspectScaleUp, 0.84f, 0.06f, 0.5f, 0.5f); - #else mSelIcon->setParam(0.96f, 0.84f, 0.06f, 0.5f, 0.5f); - #endif } } @@ -2676,11 +2681,7 @@ void dMenu_save_c::selFileCursorShow() { mSelIcon->setPos(pos.x, pos.y, mpSelData[mSelectedFile]->getPanePtr(), true); mSelIcon->setAlphaRate(1.0f); - #if TARGET_PC - mSelIcon->setParam(0.96f * mDoGph_gInf_c::hudAspectScaleUp, 0.94f, 0.03f, 0.7f, 0.7f); - #else mSelIcon->setParam(0.96f, 0.94f, 0.03f, 0.7f, 0.7f); - #endif } void dMenu_save_c::yesnoWakuAlpahAnmInit(u8 yesnoIdx, u8 startAlpha, u8 endAlpha, u8 anmTimer) { @@ -2782,7 +2783,7 @@ void dMenu_save_c::_draw() { #if TARGET_PC void dMenu_save_c::menuSaveWide() { mSaveSel.Scr->scale(mDoGph_gInf_c::hudAspectScaleUp, 1.0f); - mSaveSel.Scr->translate(mDoGph_gInf_c::getMinXF(), 0.0f); + mSaveSel.Scr->translate(mDoGph_gInf_c::getSafeMinXF(), 0.0f); mSaveSel.Scr->search(MULTI_CHAR('t_for'))->scale(mDoGph_gInf_c::hudAspectScaleDown, 1.0f); mSaveSel.Scr->search(MULTI_CHAR('t_for1'))->scale(mDoGph_gInf_c::hudAspectScaleDown, 1.0f); @@ -2813,11 +2814,20 @@ void dMenu_save_c::menuSaveWide() { mSaveSel.Scr->search(MULTI_CHAR('w_uzu07'))->scale(mDoGph_gInf_c::hudAspectScaleDown, 1.0f); mSaveSel.Scr->search(MULTI_CHAR('w_uzu08'))->scale(mDoGph_gInf_c::hudAspectScaleDown, 1.0f); mSaveSel.Scr->search(MULTI_CHAR('w_uzu09'))->scale(mDoGph_gInf_c::hudAspectScaleDown, 1.0f); + + #if TARGET_PC + if (mSelIcon) { + mSelIcon->refreshAspectScale(); + } + #endif } #endif void dMenu_save_c::_draw2() { if (field_0x21a1 == 0) { +#if TARGET_PC + saveSelAnm(); +#endif if (mpScrnExplain != NULL) { dComIfGd_set2DOpa(&mMenuSaveExplain); } diff --git a/src/d/d_menu_window.cpp b/src/d/d_menu_window.cpp index af8a734fe7..7f76f807df 100644 --- a/src/d/d_menu_window.cpp +++ b/src/d/d_menu_window.cpp @@ -32,15 +32,9 @@ public: if (getDrawFlag() == 1) { setDrawFlag(); dComIfGp_onPauseFlag(); - - #if TARGET_PC - GXSetTexCopySrc(0, 0, mDoGph_gInf_c::getWidth(), mDoGph_gInf_c::getHeight()); - #else GXSetTexCopySrc(0, 0, FB_WIDTH, FB_HEIGHT); - #endif - #if TARGET_PC - GXSetTexCopyDst(mDoGph_gInf_c::getWidth(), mDoGph_gInf_c::getHeight(), (GXTexFmt)mDoGph_gInf_c::getFrameBufferTimg()->format, GX_ENABLE); + GXSetTexCopyDst(FB_WIDTH, FB_HEIGHT, (GXTexFmt)mDoGph_gInf_c::getFrameBufferTimg()->format, GX_DISABLE); #else GXSetTexCopyDst(FB_WIDTH / 2, FB_HEIGHT / 2, (GXTexFmt)mDoGph_gInf_c::getFrameBufferTimg()->format, GX_ENABLE); #endif @@ -48,17 +42,17 @@ public: GXPixModeSync(); #if TARGET_PC // init mTexObj at capture time so the gpu ref survives window resizes - mCaptureWidth = mDoGph_gInf_c::getWidth(); - mCaptureHeight = mDoGph_gInf_c::getHeight(); - GXInitTexObj(&mTexObj, mDoGph_gInf_c::getFrameBufferTex(), mCaptureWidth, mCaptureHeight, + mCaptureWidth = JUTVideo::getManager()->getRenderWidth(); + mCaptureHeight = JUTVideo::getManager()->getRenderHeight(); + GXInitTexObj(&mTexObj, mDoGph_gInf_c::getFrameBufferTex(), FB_WIDTH / 2, FB_HEIGHT / 2, (GXTexFmt)mDoGph_gInf_c::getFrameBufferTimg()->format, GX_CLAMP, GX_CLAMP, GX_FALSE); GXInitTexObjLOD(&mTexObj, GX_LINEAR, GX_LINEAR, 0.0f, 0.0f, 0.0f, GX_FALSE, GX_FALSE, GX_ANISO_1); #endif } else { #if TARGET_PC // If the window was resized since capture, force a re-capture at the new size - if (mCaptureWidth != (u16)mDoGph_gInf_c::getWidth() || - mCaptureHeight != (u16)mDoGph_gInf_c::getHeight()) { + if (mCaptureWidth != JUTVideo::getManager()->getRenderWidth() || + mCaptureHeight != JUTVideo::getManager()->getRenderHeight()) { mFlag = 1; return; } @@ -137,8 +131,8 @@ private: /* 0x5 */ u8 mAlpha; /* 0x6 */ u8 mTopFlag; #if TARGET_PC - u16 mCaptureWidth; - u16 mCaptureHeight; + u32 mCaptureWidth; + u32 mCaptureHeight; TGXTexObj mTexObj; #endif }; @@ -705,7 +699,7 @@ void dMw_c::collect_open_proc() { dMeter2Info_set2DVibrationM(); } - if (mDoGph_gInf_c::getFader()->getStatus() == JUTFader::UNKSTATUS_0) { + if (mDoGph_gInf_c::getFader()->getStatus() == JUTFader::None) { mMenuProc = COLLECT_MOVE; } } @@ -920,7 +914,7 @@ void dMw_c::collect_letter_move_proc() { } void dMw_c::collect_letter_close_proc() { - if (mDoGph_gInf_c::getFader()->getStatus() == JUTFader::UNKSTATUS_0) { + if (mDoGph_gInf_c::getFader()->getStatus() == JUTFader::None) { mMenuProc = COLLECT_MOVE; } } @@ -952,7 +946,7 @@ void dMw_c::collect_fishing_move_proc() { } void dMw_c::collect_fishing_close_proc() { - if (mDoGph_gInf_c::getFader()->getStatus() == JUTFader::UNKSTATUS_0) { + if (mDoGph_gInf_c::getFader()->getStatus() == JUTFader::None) { mMenuProc = COLLECT_MOVE; } } @@ -983,7 +977,7 @@ void dMw_c::collect_skill_move_proc() { } void dMw_c::collect_skill_close_proc() { - if (mDoGph_gInf_c::getFader()->getStatus() == JUTFader::UNKSTATUS_0) { + if (mDoGph_gInf_c::getFader()->getStatus() == JUTFader::None) { mMenuProc = COLLECT_MOVE; } } @@ -1014,13 +1008,13 @@ void dMw_c::collect_insect_move_proc() { } void dMw_c::collect_insect_close_proc() { - if (mDoGph_gInf_c::getFader()->getStatus() == JUTFader::UNKSTATUS_0) { + if (mDoGph_gInf_c::getFader()->getStatus() == JUTFader::None) { mMenuProc = COLLECT_MOVE; } } void dMw_c::insect_open_proc() { - if (mDoGph_gInf_c::getFader()->getStatus() == JUTFader::UNKSTATUS_0) { + if (mDoGph_gInf_c::getFader()->getStatus() == JUTFader::None) { field_0x152 = 0; dComIfGp_setHeapLockFlag(1); dMw_insect_create(1); @@ -1056,7 +1050,7 @@ void dMw_c::insect_move_proc() { } void dMw_c::insect_close_proc() { - if (mDoGph_gInf_c::getFader()->getStatus() == JUTFader::UNKSTATUS_0) { + if (mDoGph_gInf_c::getFader()->getStatus() == JUTFader::None) { mMenuProc = NO_MENU; } } diff --git a/src/d/d_meter2_draw.cpp b/src/d/d_meter2_draw.cpp index 417dcad319..8e824f583b 100644 --- a/src/d/d_meter2_draw.cpp +++ b/src/d/d_meter2_draw.cpp @@ -638,12 +638,11 @@ void dMeter2Draw_c::draw() { var_f29 = g_drawHIO.mLightDrop.mDropPikariAnimSpeed_Completed; int temp_r5_2 = g_drawHIO.mLightDrop.mPikariInterval * 15; #ifdef TARGET_PC - // Set even if not advancing + // FRAME INTERP NOTE: Set even if not advancing var_f28 = g_drawHIO.mLightDrop.mPikariScaleComplete; - - const u32 ui_advance_ticks = dusk::frame_interp::get_presentation_ui_advance_ticks(); - for (u32 tick = 0; tick < ui_advance_ticks; ++tick) { + if (dusk::frame_interp::get_ui_tick_pending()) #endif + { if (field_0x756 <= temp_r5_2) { int temp_r4 = (field_0x756 % g_drawHIO.mLightDrop.mPikariInterval); int temp_r3_5 = field_0x756 / g_drawHIO.mLightDrop.mPikariInterval; @@ -669,17 +668,12 @@ void dMeter2Draw_c::draw() { } field_0x756 = -1; -#ifdef TARGET_PC - break; -#endif } else { field_0x756++; } } } -#ifdef TARGET_PC } -#endif for (int i = 0; i < 16; i++) { if (field_0x66c[i] > 0.0f) { @@ -1349,9 +1343,9 @@ void dMeter2Draw_c::drawPikari(f32 i_posX, f32 i_posY, f32* i_framep, f32 i_scal *i_framep = 0.0f; } else { #ifdef TARGET_PC - const u32 ui_advance_ticks = dusk::frame_interp::get_presentation_ui_advance_ticks(); - for (u32 i = 0; i < ui_advance_ticks; ++i) { + if (dusk::frame_interp::get_ui_tick_pending()) #endif + { *i_framep += param_8; if (*i_framep > var_f31) { if (param_9 == 1 || param_9 == 2 || param_9 == 3) { @@ -1366,9 +1360,7 @@ void dMeter2Draw_c::drawPikari(f32 i_posX, f32 i_posY, f32* i_framep, f32 i_scal } else if (*i_framep == 18.0f && param_9 == 2) { mDoAud_seStart(Z2SE_SY_ITEM_COMBINE_ICON, NULL, 0, 0); } -#ifdef TARGET_PC } -#endif playPikariBckAnimation(*i_framep); playPikariBpkAnimation(*i_framep); diff --git a/src/d/d_meter_button.cpp b/src/d/d_meter_button.cpp index 88f750d6ca..d36a124d17 100644 --- a/src/d/d_meter_button.cpp +++ b/src/d/d_meter_button.cpp @@ -16,6 +16,7 @@ #include "d/d_msg_out_font.h" #include "d/d_msg_string.h" #include "d/d_pane_class.h" +#include "dusk/frame_interpolation.h" #include #if VERSION == VERSION_GCN_JPN @@ -280,15 +281,20 @@ void dMeterButton_c::draw() { s16 temp_r6 = g_drawHIO.mEmpButton.mRepeatHitFrameNum; s16 temp_r6_2 = g_drawHIO.mEmpButton.mRepeatHitFrameNum / 2; - field_0x4b8[i]++; +#ifdef TARGET_PC + if (dusk::frame_interp::get_ui_tick_pending()) +#endif + { + field_0x4b8[i]++; - if (field_0x4b8[i] >= temp_r6) { - field_0x4b8[i] = 0; + if (field_0x4b8[i] >= temp_r6) { + field_0x4b8[i] = 0; - if (field_0x4bc[i] == 0) { - field_0x4bc[i] = 1; - } else { - field_0x4bc[i] = 0; + if (field_0x4bc[i] == 0) { + field_0x4bc[i] = 1; + } else { + field_0x4bc[i] = 0; + } } } diff --git a/src/d/d_meter_haihai.cpp b/src/d/d_meter_haihai.cpp index daf30b9aae..5ea5dad5b7 100644 --- a/src/d/d_meter_haihai.cpp +++ b/src/d/d_meter_haihai.cpp @@ -11,6 +11,7 @@ #include "d/d_com_inf_game.h" #include "d/d_meter_HIO.h" #include "d/d_pane_class.h" +#include "dusk/frame_interpolation.h" dMeterHaihai_c::dMeterHaihai_c(u8 i_type) { mType = i_type; @@ -286,14 +287,19 @@ void dMeterHaihai_c::updateHaihai() { void dMeterHaihai_c::playBckAnime(J2DAnmTransformKey* i_bck) { if (checkPlayAnime(1)) { if (i_bck != NULL) { - if (mType == 4) { - mBckFrame += g_drawHIO.mWiiLockArrowBCKAnimSpeed; - } else { - mBckFrame += g_drawHIO.mScrollArrowBCKAnimSpeed; - } +#ifdef TARGET_PC + if (dusk::frame_interp::get_ui_tick_pending()) +#endif + { + if (mType == 4) { + mBckFrame += g_drawHIO.mWiiLockArrowBCKAnimSpeed; + } else { + mBckFrame += g_drawHIO.mScrollArrowBCKAnimSpeed; + } - if (mBckFrame >= i_bck->getFrameMax()) { - mBckFrame -= i_bck->getFrameMax(); + if (mBckFrame >= i_bck->getFrameMax()) { + mBckFrame -= i_bck->getFrameMax(); + } } } else { mBtkFrame = 1.0f; @@ -309,14 +315,19 @@ void dMeterHaihai_c::playBckAnime(J2DAnmTransformKey* i_bck) { void dMeterHaihai_c::playBtkAnime(J2DAnmTextureSRTKey* i_btk) { if (checkPlayAnime(2)) { if (i_btk != NULL) { - if (mType == 4) { - mBtkFrame += g_drawHIO.mWiiLockArrowBTKAnimSpeed; - } else { - mBtkFrame += g_drawHIO.mScrollArrowBTKAnimSpeed; - } +#ifdef TARGET_PC + if (dusk::frame_interp::get_ui_tick_pending()) +#endif + { + if (mType == 4) { + mBtkFrame += g_drawHIO.mWiiLockArrowBTKAnimSpeed; + } else { + mBtkFrame += g_drawHIO.mScrollArrowBTKAnimSpeed; + } - if (mBtkFrame >= i_btk->getFrameMax()) { - mBtkFrame -= i_btk->getFrameMax(); + if (mBtkFrame >= i_btk->getFrameMax()) { + mBtkFrame -= i_btk->getFrameMax(); + } } } else { mBtkFrame = 1.0f; @@ -331,14 +342,19 @@ void dMeterHaihai_c::playBtkAnime(J2DAnmTextureSRTKey* i_btk) { void dMeterHaihai_c::playBpkAnime(J2DAnmColor* i_bpk) { if (checkPlayAnime(0)) { if (i_bpk != NULL) { - if (mType == 4) { - mBpkFrame += g_drawHIO.mWiiLockArrowBPKAnimSpeed; - } else { - mBpkFrame += g_drawHIO.mScrollArrowBPKAnimSpeed; - } +#ifdef TARGET_PC + if (dusk::frame_interp::get_ui_tick_pending()) +#endif + { + if (mType == 4) { + mBpkFrame += g_drawHIO.mWiiLockArrowBPKAnimSpeed; + } else { + mBpkFrame += g_drawHIO.mScrollArrowBPKAnimSpeed; + } - if (mBpkFrame >= i_bpk->getFrameMax()) { - mBpkFrame -= i_bpk->getFrameMax(); + if (mBpkFrame >= i_bpk->getFrameMax()) { + mBpkFrame -= i_bpk->getFrameMax(); + } } } else { mBpkFrame = 1.0f; diff --git a/src/d/d_meter_string.cpp b/src/d/d_meter_string.cpp index ad95c53edd..c28d6d5481 100644 --- a/src/d/d_meter_string.cpp +++ b/src/d/d_meter_string.cpp @@ -16,6 +16,7 @@ #include "d/d_meter2_info.h" #include "d/d_meter_HIO.h" #include "d/d_pane_class.h" +#include "dusk/frame_interpolation.h" #include dMeterString_c::dMeterString_c(int i_stringID) { @@ -105,16 +106,27 @@ void dMeterString_c::draw() { f32 var_f30 = 1.0f; if (mAnimFrame < 60.0f) { - mAnimFrame += g_drawHIO.mMiniGame.mReadyFightTextAnimSpeed; - if (mAnimFrame > 60.0f) { - mAnimFrame = 60.0f; +#if TARGET_PC + if (dusk::frame_interp::get_ui_tick_pending()) +#endif + { + mAnimFrame += g_drawHIO.mMiniGame.mReadyFightTextAnimSpeed; + if (mAnimFrame > 60.0f) { + mAnimFrame = 60.0f; + } } playBckAnimation(mAnimFrame); } else if (mAnimFrame < (f32)g_drawHIO.mMiniGame.mReadyFightTextWaitFrames + 60.0f) { - mAnimFrame += var_f30; +#if TARGET_PC + if (dusk::frame_interp::get_ui_tick_pending()) +#endif + mAnimFrame += var_f30; } else if (mAnimFrame < var_f31) { - mAnimFrame += var_f30; +#if TARGET_PC + if (dusk::frame_interp::get_ui_tick_pending()) +#endif + mAnimFrame += var_f30; var_f30 = acc(g_drawHIO.mMiniGame.field_0x172, var_f31 - mAnimFrame, 0); } @@ -128,13 +140,21 @@ void dMeterString_c::draw() { if (mPikariAnimFrame > 0.0f) { drawPikari(); } else if (mPikariAnimFrame == -1.0f && +#if TARGET_PC + dusk::frame_interp::get_ui_tick_pending() && +#endif mAnimFrame > g_drawHIO.mMiniGame.mReadyFightPikariAppearFrames) { mPikariAnimFrame = 18.0f - g_drawHIO.mMiniGame.mReadyFightPikariAnimSpeed; } - if (mAnimFrame >= var_f31) { - dMeter2Info_resetMeterString(); +#if TARGET_PC + if (dusk::frame_interp::get_ui_tick_pending()) +#endif + { + if (mAnimFrame >= var_f31) { + dMeter2Info_resetMeterString(); + } } } } diff --git a/src/d/d_msg_class.cpp b/src/d/d_msg_class.cpp index debea084dc..37085a6bb9 100644 --- a/src/d/d_msg_class.cpp +++ b/src/d/d_msg_class.cpp @@ -12,6 +12,10 @@ #include "d/d_lib.h" #include "JSystem/JUtility/JUTFont.h" +#if TARGET_PC +#include "dusk/scope_guard.hpp" +#endif + #if REGION_JPN #define CHAR_CODE_MALE_ICON 0x8189 #define CHAR_CODE_FEMALE_ICON 0x818A @@ -1918,6 +1922,11 @@ void jmessage_tSequenceProcessor::do_begin(void const* pEntry, char const* pszTe pReference->resetReference(); field_0xb5 = 0; +#if TARGET_PC + if (dusk::getSettings().game.instantText && mDoCPd_c::getHoldB(0)) { + field_0xb2 = 1; + } +#endif } void jmessage_tSequenceProcessor::do_end() { @@ -2154,6 +2163,18 @@ void jmessage_tSequenceProcessor::do_character(int iCharacter) { bool jmessage_tSequenceProcessor::do_tag(u32 i_tag, void const* i_data, u32 i_size) { jmessage_tReference* pReference = (jmessage_tReference*)getReference(); +#if TARGET_PC + // This class runs the lambda function when it goes out of scope. We want to run + // this code after the switch statement and this saves us from having to litter + // the switch statement with IF_DUSK before every return. + auto instantTextRun = SimpleScopeGuard([&]() { + if (dusk::getSettings().game.instantText && mDoCPd_c::getHoldB(0)) { + field_0xb2 = 1; + pReference->setSendTimer(0); + } + }); +#endif + switch (i_tag & 0xFF0000) { case MSGTAG_GROUP(1): { cXyz pos = pReference->getActorPos(); diff --git a/src/d/d_msg_object.cpp b/src/d/d_msg_object.cpp index 3676639908..9b7b64ebda 100644 --- a/src/d/d_msg_object.cpp +++ b/src/d/d_msg_object.cpp @@ -28,6 +28,10 @@ #include "m_Do/m_Do_lib.h" #include "JSystem/JKernel/JKRExpHeap.h" +#if TARGET_PC +#include "dusk/settings.h" +#endif + static void dMsgObject_addFundRaising(s16 param_0); static void dMsgObject_addTotalPayment(s16 param_0); @@ -1464,24 +1468,12 @@ void dMsgObject_c::fukiPosCalc(bool param_1) { fopAc_ac_c* player = dComIfGp_getPlayer(0); cXyz local_3c; cXyz cStack_48; - - #if TARGET_PC - mDoLib_project(&player->eyePos, &cStack_48, {0, 0, FB_WIDTH, FB_HEIGHT}); - #else mDoLib_project(&player->eyePos, &cStack_48); - #endif - f32 temp; if ((field_0x100->pos == cXyz(0.0f, 0.0f, 0.0f))) { temp = cStack_48.y; } else { - - #if TARGET_PC - mDoLib_project(&field_0x100->pos, &local_3c, {0, 0, FB_WIDTH, FB_HEIGHT}); - #else mDoLib_project(&field_0x100->pos, &local_3c); - #endif - if (local_3c.x >= 0.0f && local_3c.x <= FB_WIDTH && local_3c.y >= 0.0f && local_3c.y <= FB_HEIGHT) { @@ -1566,7 +1558,8 @@ u8 dMsgObject_c::isSend() { if (pRef->getSendFlag() == 5) { if (getStatusLocal() == 21) { setButtonStatusLocal(); - if (mDoCPd_c::getTrigA(0) != 0 || mDoCPd_c::getTrigB(0) != 0) { + if (IF_DUSK((dusk::getSettings().game.instantText && mDoCPd_c::getHoldB(0)) ||) + mDoCPd_c::getTrigA(0) != 0 || mDoCPd_c::getTrigB(0) != 0) { return 2; } return 0; @@ -1585,7 +1578,8 @@ u8 dMsgObject_c::isSend() { } if (pRef->getSendFlag() == 2) { setButtonStatusLocal(); - if (mDoCPd_c::getTrigA(0) != 0 || mDoCPd_c::getTrigB(0) != 0) { + if (IF_DUSK((dusk::getSettings().game.instantText && mDoCPd_c::getHoldB(0)) ||) + mDoCPd_c::getTrigA(0) != 0 || mDoCPd_c::getTrigB(0) != 0) { return 2; } } @@ -1598,7 +1592,8 @@ u8 dMsgObject_c::isSend() { return 2; } } else { - if (mDoCPd_c::getTrigA(0) != 0 || mDoCPd_c::getTrigB(0) != 0) { + if (IF_DUSK((dusk::getSettings().game.instantText && mDoCPd_c::getHoldB(0)) ||) + mDoCPd_c::getTrigA(0) != 0 || mDoCPd_c::getTrigB(0) != 0) { return 2; } if (mesgCancelButton) { diff --git a/src/d/d_msg_out_font.cpp b/src/d/d_msg_out_font.cpp index 4d15b069d3..efc655d529 100644 --- a/src/d/d_msg_out_font.cpp +++ b/src/d/d_msg_out_font.cpp @@ -5,6 +5,7 @@ #include "JSystem/JUtility/JUTTexture.h" #include "d/d_meter2_info.h" #include "d/d_msg_object.h" +#include "dusk/frame_interpolation.h" #include "f_op/f_op_msg_mng.h" COutFontSet_c::COutFontSet_c() { @@ -311,6 +312,15 @@ void COutFont_c::draw(J2DTextBox* i_textbox, f32 param_1, f32 param_2, f32 param sp256[i] = field_0x1b4[i]; } +#ifdef TARGET_PC + bool uiTickPending = dusk::frame_interp::get_ui_tick_pending(); + if (!uiTickPending) { + for (int i = 0; i < 70; i++) { + sp256[i] = -1; + } + } +#endif + for (int i = 0; i < 35; i++) { u8 type = mpOfs[i]->getType(); J2DTextBox* tbox = mpOfs[i]->getTextBoxPtr(); @@ -505,9 +515,14 @@ void COutFont_c::draw(J2DTextBox* i_textbox, f32 param_1, f32 param_2, f32 param case 20: case 21: case 22: - field_0x1b4[type]++; - if (field_0x1b4[type] >= 28) { - field_0x1b4[type] = 0; +#ifdef TARGET_PC + if (uiTickPending) +#endif + { + field_0x1b4[type]++; + if (field_0x1b4[type] >= 28) { + field_0x1b4[type] = 0; + } } mpPane[type]->rotate(0.5f * sizeX, 0.5f * sizeY, ROTATE_Z, diff --git a/src/d/d_msg_scrn_howl.cpp b/src/d/d_msg_scrn_howl.cpp index 03729377a5..1d39cba1a1 100644 --- a/src/d/d_msg_scrn_howl.cpp +++ b/src/d/d_msg_scrn_howl.cpp @@ -607,17 +607,10 @@ void dMsgScrnHowl_c::drawGuide() { J2DGrafContext* grafContext = dComIfGp_getCurrentGrafPort(); Vec local_b0 = field_0x128; Vec local_bc = field_0x140; -#if TARGET_PC - grafContext->scissor( - (local_b0.x - mDoGph_gInf_c::getMinXF()) / (mDoGph_gInf_c::getWidthF() / mDoGph_gInf_c::getWidth()), - field_0x2118, (local_bc.x - local_b0.x) / (mDoGph_gInf_c::getWidthF() / mDoGph_gInf_c::getWidth()), - field_0x2120); -#else grafContext->scissor( (local_b0.x - mDoGph_gInf_c::getMinXF()) / (mDoGph_gInf_c::getWidthF() / FB_WIDTH), field_0x2118, (local_bc.x - local_b0.x) / (mDoGph_gInf_c::getWidthF() / FB_WIDTH), field_0x2120); -#endif grafContext->setScissor(); f32 local_cc = mpLineH[0]->getGlobalPosX(); s16 sVar12 = 0; @@ -745,19 +738,11 @@ void dMsgScrnHowl_c::drawGuide2() { } Vec local_58 = field_0x128; Vec local_64 = field_0x140; -#if TARGET_PC - f32 local_70 = mDoGph_gInf_c::getHeightF() / mDoGph_gInf_c::getHeight(); - grafContext->scissor( - (local_58.x - mDoGph_gInf_c::getMinXF()) / (mDoGph_gInf_c::getWidthF() / mDoGph_gInf_c::getWidth()), - field_0x2118, (local_64.x - local_58.x) / (mDoGph_gInf_c::getWidthF() / mDoGph_gInf_c::getWidth()), - field_0x2120); -#else f32 local_70 = mDoGph_gInf_c::getHeightF() / mDoGph_gInf_c::getHeight(); grafContext->scissor( (local_58.x - mDoGph_gInf_c::getMinXF()) / (mDoGph_gInf_c::getWidthF() / FB_WIDTH), field_0x2118, (local_64.x - local_58.x) / (mDoGph_gInf_c::getWidthF() / FB_WIDTH), field_0x2120); -#endif grafContext->setScissor(); f32 local_74 = mpLineH[0]->getGlobalPosX(); s16 local_134 = 0; @@ -859,15 +844,9 @@ void dMsgScrnHowl_c::drawEffect() { Vec vec1 = field_0x128; Vec vec2 = field_0x140; mDoGph_gInf_c::getHeightF(); -#if TARGET_PC - grafContext->scissor( - (vec1.x - mDoGph_gInf_c::getMinXF()) / (mDoGph_gInf_c::getWidthF() / mDoGph_gInf_c::getWidth()), field_0x2118, - 12.0f + ((vec2.x - vec1.x) / (mDoGph_gInf_c::getWidthF() / mDoGph_gInf_c::getWidth())), field_0x2120); -#else grafContext->scissor( (vec1.x - mDoGph_gInf_c::getMinXF()) / (mDoGph_gInf_c::getWidthF() / FB_WIDTH), field_0x2118, 12.0f + ((vec2.x - vec1.x) / (mDoGph_gInf_c::getWidthF() / FB_WIDTH)), field_0x2120); -#endif grafContext->setScissor(); u8 timer = daAlink_getAlinkActorClass()->getWolfHowlMgrP()->getReleaseTimer(); u8 screenAlpha = mpScreen->search(MULTI_CHAR('line00'))->getAlpha(); diff --git a/src/d/d_msg_scrn_item.cpp b/src/d/d_msg_scrn_item.cpp index 53ef97607c..672700682d 100644 --- a/src/d/d_msg_scrn_item.cpp +++ b/src/d/d_msg_scrn_item.cpp @@ -557,22 +557,11 @@ void dMsgScrnItem_c::fukiPosCalc(u8 param_1) { cXyz local_70; cXyz cStack_7c; f32 f3; - - #if TARGET_PC - mDoLib_project(&player->eyePos, &cStack_7c, {0, 0, FB_WIDTH, FB_HEIGHT}); - #else mDoLib_project(&player->eyePos, &cStack_7c); - #endif - if (iVar6->pos == cXyz(0.0f, 0.0f, 0.0f)) { f3 = cStack_7c.y; } else { - #if TARGET_PC - mDoLib_project(&iVar6->pos, &local_70, {0, 0, FB_WIDTH, FB_HEIGHT}); - #else mDoLib_project(&iVar6->pos, &local_70); - #endif - if (local_70.x >= 0.0f && local_70.x <= FB_WIDTH && local_70.y >= 0.0f && local_70.y <= FB_HEIGHT) { diff --git a/src/d/d_msg_scrn_light.cpp b/src/d/d_msg_scrn_light.cpp index c34397aad2..19671b5d57 100644 --- a/src/d/d_msg_scrn_light.cpp +++ b/src/d/d_msg_scrn_light.cpp @@ -204,16 +204,14 @@ void dMsgScrnLight_c::draw(f32* i_anmFrame, f32 i_posX, f32 i_posY, f32 i_scaleX if (mPlayAnim) { #ifdef TARGET_PC - const u32 ui_advance_ticks = dusk::frame_interp::get_presentation_ui_advance_ticks(); - for (u32 i = 0; i < ui_advance_ticks; ++i) { + if (dusk::frame_interp::get_ui_tick_pending()) #endif + { *i_anmFrame += 1.0f; if (*i_anmFrame >= mpBck->getFrameMax()) { *i_anmFrame = 0.0f; } -#ifdef TARGET_PC } -#endif mBckFrame = *i_anmFrame; mBpkFrame = *i_anmFrame; @@ -229,17 +227,15 @@ void dMsgScrnLight_c::draw(f32* i_anmFrame, f32 i_posX, f32 i_posY, f32 i_scaleX if (mPlayAnim) { #ifdef TARGET_PC - const u32 ui_advance_ticks = dusk::frame_interp::get_presentation_ui_advance_ticks(); - for (u32 i = 0; i < ui_advance_ticks; ++i) { + if (dusk::frame_interp::get_ui_tick_pending()) #endif + { *i_anmFrame += i_anmRate; if (*i_anmFrame >= mpBck->getFrameMax()) { *i_anmFrame = 0.0f; } -#ifdef TARGET_PC } -#endif mBckFrame = *i_anmFrame; mBpkFrame = *i_anmFrame; diff --git a/src/d/d_msg_scrn_talk.cpp b/src/d/d_msg_scrn_talk.cpp index 68b471a14d..c9f925e784 100644 --- a/src/d/d_msg_scrn_talk.cpp +++ b/src/d/d_msg_scrn_talk.cpp @@ -441,22 +441,11 @@ void dMsgScrnTalk_c::fukiPosCalc(u8 param_1) { cXyz local_70; cXyz cStack_7c; f32 f3y; - - #if TARGET_PC - mDoLib_project(&player->eyePos, &cStack_7c, {0, 0, FB_WIDTH, FB_HEIGHT}); - #else mDoLib_project(&player->eyePos, &cStack_7c); - #endif - if (msgActor->pos == cXyz(0.0f, 0.0f, 0.0f)) { f3y = cStack_7c.y; } else { - #if TARGET_PC - mDoLib_project(&msgActor->pos, &local_70, {0, 0, FB_WIDTH, FB_HEIGHT}); - #else mDoLib_project(&msgActor->pos, &local_70); - #endif - if (local_70.x >= 0.0f && local_70.x <= FB_WIDTH_BASE && local_70.y >= 0.0f && local_70.y <= FB_HEIGHT_BASE) { diff --git a/src/d/d_msg_unit.cpp b/src/d/d_msg_unit.cpp index 98dd33b4a8..f773abd373 100644 --- a/src/d/d_msg_unit.cpp +++ b/src/d/d_msg_unit.cpp @@ -271,6 +271,30 @@ void dMsgUnit_c::setTag(int i_type, int i_value, char* o_buffer, bool param_4) { u32 filesize = pHeader->size; u8* pSection = ((u8*)pHeader) + filepos; + #if TARGET_PC + // patch bug in the original game where filepos would be incremented by the next section's size rather than the current section size. + // in certain scenarios this would read past the end of the file, incrementing filepos by 0 in an infinite loop + while (filepos < filesize) { + switch (((bmg_section_t*)pSection)->magic) { + case 'FLW1': + break; + case 'FLI1': + break; + case 'INF1': + pInfoBlock = (bmg_section_t*)pSection; + break; + case 'DAT1': + pMsgDataBlock = pSection; + break; + case 'STR1': + pStrAttributeBlock = (str1_section_t*)pSection; + break; + } + + filepos += ((bmg_section_t*)pSection)->size; + pSection += ((bmg_section_t*)pSection)->size; + } + #else for (; filepos < filesize; filepos += ((bmg_section_t*)pSection)->size) { switch (((bmg_section_t*)pSection)->magic) { case 'FLW1': @@ -289,6 +313,7 @@ void dMsgUnit_c::setTag(int i_type, int i_value, char* o_buffer, bool param_4) { } pSection += ((bmg_section_t*)pSection)->size; } + #endif // This section is weird. The debug seems like entriesStr is outside the condition // but the normal build doesn't really work with that. Same for pInfoBlock->entries. diff --git a/src/d/d_name.cpp b/src/d/d_name.cpp index 120bcff752..e4232eff34 100644 --- a/src/d/d_name.cpp +++ b/src/d/d_name.cpp @@ -1288,7 +1288,11 @@ void dName_c::selectCursorPosSet(int row) { #if TARGET_PC void dName_c::nameWide() { //Resize Select Icon - mSelIcon->setParam(0.82f * mDoGph_gInf_c::hudAspectScaleUp, 0.77f, 0.05f, 0.4f, 0.4f); + #if TARGET_PC + if (mSelIcon) { + mSelIcon->refreshAspectScale(); + } + #endif // List of Characters Box static u64 l_tagName[65] = { @@ -1540,11 +1544,7 @@ void dName_c::screenSet() { mSelIcon = JKR_NEW dSelect_cursor_c(0, 1.0f, NULL); JUT_ASSERT(0, mSelIcon != NULL); - #if TARGET_PC - mSelIcon->setParam(0.82f * mDoGph_gInf_c::hudAspectScaleUp, 0.77f, 0.05f, 0.4f, 0.4f); - #else mSelIcon->setParam(0.82f, 0.77f, 0.05f, 0.4f, 0.4f); - #endif Vec pos = mMojiIcon[mCharRow + mCharColumn * 5]->getGlobalVtxCenter(false, 0); mSelIcon->setPos(pos.x, pos.y, mMojiIcon[mCharRow + mCharColumn * 5]->getPanePtr(), true); diff --git a/src/d/d_ovlp_fade.cpp b/src/d/d_ovlp_fade.cpp index 1ae49ab74a..43732cf4be 100644 --- a/src/d/d_ovlp_fade.cpp +++ b/src/d/d_ovlp_fade.cpp @@ -26,8 +26,8 @@ static void dOvlpFd_startFadeIn(int param_0) { JUTFader* fader = JFWDisplay::getManager()->getFader(); JUT_ASSERT(0, fader != NULL); - fader->setStatus(JUTFader::UNKSTATUS_0, 0); - fader->setStatus(JUTFader::UNKSTATUS_0, -1); + fader->setStatus(JUTFader::None, 0); + fader->setStatus(JUTFader::None, -1); fader->startFadeIn(param_0); } diff --git a/src/d/d_ovlp_fade2.cpp b/src/d/d_ovlp_fade2.cpp index 37dbeedba2..c6c6cc735e 100644 --- a/src/d/d_ovlp_fade2.cpp +++ b/src/d/d_ovlp_fade2.cpp @@ -12,13 +12,8 @@ #include "m_Do/m_Do_graphic.h" void dOvlpFd2_dlst_c::draw() { -#if TARGET_PC - GXSetViewport(0.0f, 0.0f, mDoGph_gInf_c::getWidth(), mDoGph_gInf_c::getHeight(), 0.0f, 1.0f); - GXSetScissor(0, 0, mDoGph_gInf_c::getWidth(), mDoGph_gInf_c::getHeight()); -#else GXSetViewport(0.0f, 0.0f, FB_WIDTH, FB_HEIGHT, 0.0f, 1.0f); GXSetScissor(0, 0, FB_WIDTH, FB_HEIGHT); -#endif GXSetVtxAttrFmt(GX_VTXFMT0, GX_VA_POS, GX_CLR_RGB, GX_RGBA4, 0); GXClearVtxDesc(); GXSetVtxDesc(GX_VA_POS, GX_DIRECT); diff --git a/src/d/d_ovlp_fade3.cpp b/src/d/d_ovlp_fade3.cpp index d6f432f3ee..0b3c03db2b 100644 --- a/src/d/d_ovlp_fade3.cpp +++ b/src/d/d_ovlp_fade3.cpp @@ -13,11 +13,10 @@ #include "m_Do/m_Do_graphic.h" void dDlst_snapShot_c::draw() { -#if TARGET_PC - GXSetTexCopySrc(0, 0, mDoGph_gInf_c::getWidth(), mDoGph_gInf_c::getHeight()); - GXSetTexCopyDst(mDoGph_gInf_c::getWidth(), mDoGph_gInf_c::getHeight(), GX_TF_RGBA8, GX_TRUE); -#else GXSetTexCopySrc(0, 0, FB_WIDTH, FB_HEIGHT); +#if TARGET_PC + GXSetTexCopyDst(FB_WIDTH, FB_HEIGHT, GX_TF_RGBA8, GX_FALSE); +#else GXSetTexCopyDst(FB_WIDTH / 2, FB_HEIGHT / 2, GX_TF_RGBA8, GX_TRUE); #endif GXCopyTex(mDoGph_gInf_c::getFrameBufferTex(), GX_FALSE); @@ -25,13 +24,8 @@ void dDlst_snapShot_c::draw() { } void dOvlpFd3_dlst_c::draw() { -#if TARGET_PC - GXSetViewport(0.0f, 0.0f, mDoGph_gInf_c::getWidth(), mDoGph_gInf_c::getHeight(), 0.0f, 1.0f); - GXSetScissor(0, 0, mDoGph_gInf_c::getWidth(), mDoGph_gInf_c::getHeight()); -#else GXSetViewport(0.0f, 0.0f, FB_WIDTH, FB_HEIGHT, 0.0f, 1.0f); GXSetScissor(0, 0, FB_WIDTH, FB_HEIGHT); -#endif GXSetVtxAttrFmt(GX_VTXFMT0, GX_VA_POS, GX_CLR_RGB, GX_RGBA4, 0); GXClearVtxDesc(); GXSetVtxDesc(GX_VA_POS, GX_DIRECT); @@ -112,19 +106,6 @@ void dOvlpFd3_dlst_c::draw() { GXBegin(GX_QUADS, GX_VTXFMT0, 4); - #if TARGET_PC - GXPosition2s16(-FB_WIDTH / 2, FB_HEIGHT / 2); - GXTexCoord2s8(0, 0); - - GXPosition2s16(FB_WIDTH / 2, FB_HEIGHT / 2); - GXTexCoord2s8(1, 0); - - GXPosition2s16(FB_WIDTH / 2, -FB_HEIGHT / 2); - GXTexCoord2s8(1, 1); - - GXPosition2s16(-FB_WIDTH / 2, -FB_HEIGHT / 2); - GXTexCoord2s8(0, 1); - #else GXPosition2s16(-mDoGph_gInf_c::getWidth() / 2, mDoGph_gInf_c::getHeight() / 2); GXTexCoord2s8(0, 0); @@ -136,7 +117,6 @@ void dOvlpFd3_dlst_c::draw() { GXPosition2s16(-mDoGph_gInf_c::getWidth() / 2, -mDoGph_gInf_c::getHeight() / 2); GXTexCoord2s8(0, 1); - #endif GXEnd(); diff --git a/src/d/d_pane_class.cpp b/src/d/d_pane_class.cpp index 78855b2d1c..4d5104ffcf 100644 --- a/src/d/d_pane_class.cpp +++ b/src/d/d_pane_class.cpp @@ -356,12 +356,8 @@ Vec CPaneMgr::getGlobalVtx(J2DPane* p_pane, Mtx* param_1, u8 param_2, bool param Mtx m; MtxP mp = (MtxP)param_1; J2DPane* parent = p_pane->getParentPane(); - -#if TARGET_PC - J2DOrthoGraph ortho(0.0f, 0.0f, mDoGph_gInf_c::getWidth(), mDoGph_gInf_c::getHeight(), -1.0f, 1.0f); -#else + J2DOrthoGraph ortho(0.0f, 0.0f, FB_WIDTH, FB_HEIGHT, -1.0f, 1.0f); -#endif ortho.setOrtho(mDoGph_gInf_c::getMinXF(), mDoGph_gInf_c::getMinYF(), mDoGph_gInf_c::getWidthF(), mDoGph_gInf_c::getHeightF(), -1.0f, 1.0f); if (parent != NULL) { diff --git a/src/d/d_resorce.cpp b/src/d/d_resorce.cpp index 0bf537bfd7..73f031beae 100644 --- a/src/d/d_resorce.cpp +++ b/src/d/d_resorce.cpp @@ -296,7 +296,11 @@ J3DModelData* dRes_info_c::loaderBasicBmd(u32 i_tag, void* i_data) { addWarpMaterial(modelData); } - if (i_tag == 'BMDR' || i_tag == 'BMWR') { + // FRAME INTERP NOTE: Always create shared DL buffers so we can use J3DMaterial::diff() +#ifndef TARGET_PC + if (i_tag == 'BMDR' || i_tag == 'BMWR') +#endif + { s32 result = modelData->newSharedDisplayList(J3DMdlFlag_UseSingleDL); if (result != kJ3DError_Success) { return NULL; diff --git a/src/d/d_s_play.cpp b/src/d/d_s_play.cpp index 3f3cd073b9..38308d0668 100644 --- a/src/d/d_s_play.cpp +++ b/src/d/d_s_play.cpp @@ -1418,12 +1418,7 @@ static int phase_4(dScnPly_c* i_this) { dComIfGp_setPlayerPtr(i, NULL); } -#if TARGET_PC - dComIfGp_setWindow(0, 0.0f, 0.0f, mDoGph_gInf_c::getWidth(), mDoGph_gInf_c::getHeight(), 0.0f, - 1.0f, 0, 2); -#else dComIfGp_setWindow(0, 0.0f, 0.0f, FB_WIDTH, FB_HEIGHT, 0.0f, 1.0f, 0, 2); -#endif dComIfGp_setCameraInfo(0, NULL, 0, 0, -1); dComIfGd_setWindow(NULL); dComIfGd_setViewport(NULL); diff --git a/src/d/d_select_cursor.cpp b/src/d/d_select_cursor.cpp index 2cdd454737..0773433b4d 100644 --- a/src/d/d_select_cursor.cpp +++ b/src/d/d_select_cursor.cpp @@ -69,6 +69,9 @@ dSelect_cursor_c::dSelect_cursor_c(u8 param_0, f32 param_1, JKRArchive* param_2) field_0x84[i] = 0.0f; } mParam1 = mpCursorHIO->mXAxisExpansion; +#ifdef TARGET_PC + mBaseParam1 = mParam1; +#endif mParam2 = mpCursorHIO->mYAxisExpansion; mParam3 = mpCursorHIO->mOscillation; mParam4 = mpCursorHIO->mRatioX; @@ -259,6 +262,13 @@ void dSelect_cursor_c::update() { if (field_0xb6 == 3) { fVar1 = 0.5f; } +#ifdef TARGET_PC + if (mpPane) { + Vec pos = mpPaneMgr->getGlobalVtxCenter(mpPane, false, 0); + mPositionX = pos.x; + mPositionY = pos.y; + } +#endif mpPaneMgr->translate(mPositionX, mPositionY); if (mpCursorHIO->mDebugON) { mParam1 = mpCursorHIO->mXAxisExpansion; @@ -272,9 +282,9 @@ void dSelect_cursor_c::update() { if (field_0x30) { if (chkPlayAnime(0)) { #ifdef TARGET_PC - const u32 ui_advance_ticks = dusk::frame_interp::get_presentation_ui_advance_ticks(); - for (u32 tick = 0; tick < ui_advance_ticks; ++tick) { + if (dusk::frame_interp::get_ui_tick_pending()) #endif + { if (mNameIdx == 1) { field_0x44 += mpCursorHIO->field_0x8 * fVar1; } else { @@ -284,9 +294,7 @@ void dSelect_cursor_c::update() { if (field_0x44 >= field_0x30->getFrameMax()) { field_0x44 -= field_0x30->getFrameMax(); } -#ifdef TARGET_PC } -#endif field_0x30->setFrame(field_0x44); setBpkAnimation(field_0x30); @@ -303,9 +311,9 @@ void dSelect_cursor_c::update() { if (field_0x34[i]) { if ((i == 0 && chkPlayAnime(2)) || (i == 1 && chkPlayAnime(3))) { #ifdef TARGET_PC - const u32 ui_advance_ticks = dusk::frame_interp::get_presentation_ui_advance_ticks(); - for (u32 tick = 0; tick < ui_advance_ticks; ++tick) { + if (dusk::frame_interp::get_ui_tick_pending()) #endif + { if (mNameIdx == 1) { field_0x48[i] += mpCursorHIO->field_0x8 * fVar1; } else { @@ -314,9 +322,7 @@ void dSelect_cursor_c::update() { if (field_0x48[i] >= field_0x34[i]->getFrameMax()) { field_0x48[i] -= field_0x34[i]->getFrameMax(); } -#ifdef TARGET_PC } -#endif field_0x34[i]->setFrame(field_0x48[i]); } @@ -326,9 +332,9 @@ void dSelect_cursor_c::update() { if (field_0x2C && chkPlayAnime(1)) { #ifdef TARGET_PC - const u32 ui_advance_ticks = dusk::frame_interp::get_presentation_ui_advance_ticks(); - for (u32 tick = 0; tick < ui_advance_ticks; ++tick) { + if (dusk::frame_interp::get_ui_tick_pending()) #endif + { if (mNameIdx == 1) { field_0x40 += mpCursorHIO->field_0x8 * fVar1; } else { @@ -337,9 +343,7 @@ void dSelect_cursor_c::update() { if (field_0x40 >= field_0x2C->getFrameMax()) { field_0x40 -= field_0x2C->getFrameMax(); } -#ifdef TARGET_PC } -#endif field_0x2C->setFrame(field_0x40); setBckAnimation(field_0x2C); @@ -348,13 +352,11 @@ void dSelect_cursor_c::update() { if (chkPlayAnime(1) && mNameIdx == 0) { #ifdef TARGET_PC - const u32 ui_advance_ticks = dusk::frame_interp::get_presentation_ui_advance_ticks(); - for (u32 tick = 0; tick < ui_advance_ticks; ++tick) { + if (dusk::frame_interp::get_ui_tick_pending()) #endif + { setCursorAnimation(); -#ifdef TARGET_PC } -#endif } mpScreen->animation(); @@ -412,6 +414,9 @@ void dSelect_cursor_c::setPos(f32 i_posX, f32 i_posY, J2DPane* i_pane, bool i_sc void dSelect_cursor_c::setParam(f32 i_param1, f32 i_param2, f32 i_param3, f32 i_param4, f32 i_param5) { mParam1 = i_param1; +#ifdef TARGET_PC + mBaseParam1 = i_param1; +#endif mParam2 = i_param2; mParam3 = i_param3; mParam4 = i_param4; @@ -570,3 +575,9 @@ void dSelect_cursor_c::setBckAnimation(J2DAnmTransformKey* param_0) { void dSelect_cursor_c::moveCenter(J2DPane* i_pane, f32 i_x, f32 i_y) { i_pane->translate(i_x,i_y); } + +#ifdef TARGET_PC +void dSelect_cursor_c::refreshAspectScale() { + mParam1 = mBaseParam1 * mDoGph_gInf_c::hudAspectScaleUp; +} +#endif diff --git a/src/d/d_select_icon.cpp b/src/d/d_select_icon.cpp index 3a4671fefa..32be9c15e2 100644 --- a/src/d/d_select_icon.cpp +++ b/src/d/d_select_icon.cpp @@ -9,9 +9,9 @@ dSi_HIO_c::dSi_HIO_c() {} void dSelect_icon_c::animation() { if (field_0x10->getAlpha() != 0) { #ifdef TARGET_PC - const u32 ui_advance_ticks = dusk::frame_interp::get_presentation_ui_advance_ticks(); - for (u32 i = 0; i < ui_advance_ticks; ++i) { + if (dusk::frame_interp::get_ui_tick_pending()) #endif + { field_0x20 += field_0x2c; if (field_0x20 >= field_0x1c->getFrameMax()) { field_0x20 = 0.0f; @@ -22,9 +22,9 @@ void dSelect_icon_c::animation() { if (field_0x28 >= field_0x24->getFrameMax()) { field_0x28 = 0.0f; } -#ifdef TARGET_PC } - // Set even if not advancing +#ifdef TARGET_PC + // FRAME INTERP NOTE: Set even if not advancing field_0x1c->setFrame(field_0x20); #endif diff --git a/src/d/d_shop_system.cpp b/src/d/d_shop_system.cpp index f1636bb093..5871d65d2a 100644 --- a/src/d/d_shop_system.cpp +++ b/src/d/d_shop_system.cpp @@ -855,11 +855,7 @@ int dShopSystem_c::seq_wait(fopAc_ac_c* param_0, dMsgFlow_c* param_1) { } inline void pos3Dto2D(Vec* a, Vec* b) { -#if TARGET_PC - mDoLib_project(a, b, {0, 0, FB_WIDTH, FB_HEIGHT}); -#else mDoLib_project(a, b); -#endif } int dShopSystem_c::seq_start(fopAc_ac_c* actor, dMsgFlow_c* i_flow) { @@ -1572,10 +1568,22 @@ BOOL dShopSystem_c::checkShopOpen() { } bool dShopSystem_c::checkLeftTrigger(STControl* i_stick) { +#if TARGET_PC + if (dusk::getSettings().game.enableMirrorMode) { + return i_stick->checkRightTrigger(); + } +#endif + return i_stick->checkLeftTrigger(); } bool dShopSystem_c::checkRightTrigger(STControl* i_stick) { +#if TARGET_PC + if (dusk::getSettings().game.enableMirrorMode) { + return i_stick->checkLeftTrigger(); + } +#endif + return i_stick->checkRightTrigger(); } diff --git a/src/d/d_timer.cpp b/src/d/d_timer.cpp index 552874b552..cb40e3444a 100644 --- a/src/d/d_timer.cpp +++ b/src/d/d_timer.cpp @@ -23,9 +23,7 @@ #include "m_Do/m_Do_lib.h" #include -#if TARGET_PC #include "dusk/frame_interpolation.h" -#endif static int dTimer_createStart2D(s32 param_0, u16 param_1); @@ -1340,27 +1338,22 @@ void dDlst_TimerScrnDraw_c::draw() { f32 temp = (f32)g_drawHIO.mMiniGame.mGetInTextAlphaFrames + ((f32)g_drawHIO.mMiniGame.mGetInTextWaitFrames + 60.0f); -#if TARGET_PC - const u32 pending_ui_ticks = dusk::frame_interp::get_presentation_ui_advance_ticks(); -#else - const u32 pending_ui_ticks = 1u; -#endif - for (int i = 0; i < 51; i++) { - for (u32 tick = 0; tick < pending_ui_ticks; tick++) { - if (!(m_getin_info[i].bck_frame > 0.0f && m_getin_info[i].bck_frame < temp)) { - break; - } - - if (m_getin_info[i].bck_frame < 60.0f) { - m_getin_info[i].bck_frame += g_drawHIO.mMiniGame.mGetInTextAnimSpeed; - if (m_getin_info[i].bck_frame > 60.0f) { - m_getin_info[i].bck_frame = 60.0f; +#if TARGET_PC + if (dusk::frame_interp::get_ui_tick_pending()) +#endif + { + if (m_getin_info[i].bck_frame > 0.0f && m_getin_info[i].bck_frame < temp) { + if (m_getin_info[i].bck_frame < 60.0f) { + m_getin_info[i].bck_frame += g_drawHIO.mMiniGame.mGetInTextAnimSpeed; + if (m_getin_info[i].bck_frame > 60.0f) { + m_getin_info[i].bck_frame = 60.0f; + } + } else if (m_getin_info[i].bck_frame < g_drawHIO.mMiniGame.mGetInTextWaitFrames + 60.0f) { + m_getin_info[i].bck_frame++; + } else if (m_getin_info[i].bck_frame < temp) { + m_getin_info[i].bck_frame++; } - } else if (m_getin_info[i].bck_frame < g_drawHIO.mMiniGame.mGetInTextWaitFrames + 60.0f) { - m_getin_info[i].bck_frame++; - } else if (m_getin_info[i].bck_frame < temp) { - m_getin_info[i].bck_frame++; } } @@ -1369,7 +1362,7 @@ void dDlst_TimerScrnDraw_c::draw() { if (m_getin_info[i].bck_frame >= g_drawHIO.mMiniGame.mGetInTextWaitFrames + 60.0f && m_getin_info[i].bck_frame < temp) { var_f29 = acc(g_drawHIO.mMiniGame.mGetInTextAlphaFrames, - temp - m_getin_info[i].bck_frame, 0); + temp - m_getin_info[i].bck_frame, 0); } if (m_getin_info[i].bck_frame < 60.0f) { @@ -1382,7 +1375,7 @@ void dDlst_TimerScrnDraw_c::draw() { if (g_drawHIO.mMiniGame.mGetInTextLocation == 1) { mpGetInRoot->translate(m_getin_info[i].pos_x + g_drawHIO.mMiniGame.mGetInTextPosX, - m_getin_info[i].pos_y + g_drawHIO.mMiniGame.mGetInTextPosY); + m_getin_info[i].pos_y + g_drawHIO.mMiniGame.mGetInTextPosY); } else { f32 temp_f2 = m_getin_info[i].bck_frame - 40.0f; f32 var_f3 = ((temp_f2 * 0.5f) * temp_f2) * 0.15f; @@ -1396,20 +1389,25 @@ void dDlst_TimerScrnDraw_c::draw() { } mpGetInRoot->scale(g_drawHIO.mMiniGame.mGetInTextSizeX, - g_drawHIO.mMiniGame.mGetInTextSizeY); + g_drawHIO.mMiniGame.mGetInTextSizeY); mpGetInScreen->draw(0.0f, 0.0f, graf_ctx); if (m_getin_info[i].pikari_frame > 0.0f) { drawPikari(i); } else if (m_getin_info[i].pikari_frame == -1.0f) { - if (m_getin_info[i].field_0xc == 0) { - if (m_getin_info[i].bck_frame > g_drawHIO.mMiniGame.mGetInPikariAppearFrames) { +#if TARGET_PC + if (dusk::frame_interp::get_ui_tick_pending()) +#endif + { + if (m_getin_info[i].field_0xc == 0) { + if (m_getin_info[i].bck_frame > g_drawHIO.mMiniGame.mGetInPikariAppearFrames) { + m_getin_info[i].pikari_frame = + 18.0f - g_drawHIO.mMiniGame.mGetInPikariAnimSpeed; + } + } else if (m_getin_info[i].bck_frame > g_drawHIO.mMiniGame.mStartPikariAppearFrames) { m_getin_info[i].pikari_frame = - 18.0f - g_drawHIO.mMiniGame.mGetInPikariAnimSpeed; + 18.0f - g_drawHIO.mMiniGame.mStartPikariAnimSpeed; } - } else if (m_getin_info[i].bck_frame > g_drawHIO.mMiniGame.mStartPikariAppearFrames) { - m_getin_info[i].pikari_frame = - 18.0f - g_drawHIO.mMiniGame.mStartPikariAnimSpeed; } } } diff --git a/src/dusk/audio/DuskAudioSystem.cpp b/src/dusk/audio/DuskAudioSystem.cpp index c818e46738..3577d3c9c7 100644 --- a/src/dusk/audio/DuskAudioSystem.cpp +++ b/src/dusk/audio/DuskAudioSystem.cpp @@ -77,6 +77,14 @@ void dusk::audio::SetMasterVolume(const f32 value) { MasterVolume = value; } +void dusk::audio::SetPaused(const bool paused) { + if (paused) { + SDL_PauseAudioStreamDevice(PlaybackStream); + } else { + SDL_ResumeAudioStreamDevice(PlaybackStream); + } +} + void dusk::audio::SetEnableReverb(const bool value) { JASCriticalSection section; diff --git a/src/dusk/audio/DuskDsp.cpp b/src/dusk/audio/DuskDsp.cpp index bcad272a47..f576a5d0f1 100644 --- a/src/dusk/audio/DuskDsp.cpp +++ b/src/dusk/audio/DuskDsp.cpp @@ -5,14 +5,15 @@ #include #include +#include #include #include #include "Adpcm.hpp" #include "freeverb/revmodel.hpp" -#include "JSystem/JAudio2/JASDriverIF.h" #include "dusk/audio/DuskAudioSystem.h" #include "dusk/endian.h" +#include "dusk/logging.h" #include "global.h" #include "tracy/Tracy.hpp" @@ -95,6 +96,13 @@ static void RenderChannel( ChannelAuxData& channelAux, OutputSubframe& subframe); +static void RenderOutputChannel( + const JASDsp::TChannel& sourceChannel, + ChannelAuxData& aux, + OutputChannel outputChannel, + const std::span inputSamples, + OutputSubframe& fullOutputSubframe); + /** * Converts a pitch value on a DSP channel to a sample rate. */ @@ -117,6 +125,8 @@ static void ResetChannel(JASDsp::TChannel& channel, ChannelAuxData& aux) { aux.resamplePos = 0.0; aux.resamplePrev = 0; + aux.oscPhase = 0; + aux.prev_lp_out = 0.0f; aux.prev_lp_in = 0.0f; @@ -141,6 +151,119 @@ static void MixSubframe(DspSubframe& dst, const DspSubframe& src) { } } +enum class OscType : u16 { + SQUARE_WAVE_PW_50 = 0, + SAW_WAVE = 1, + SQUARE_WAVE_PW_25 = 3, + TRIANGLE_WAVE = 4, + // idk what 5 and 6 are + SINE_WAVE = 7, + // idk what 8 and 9 are + SINE_WAVE_VAR_STEP = 10, + EVOLVING_HARMONIC = 11, + EVOLVING_RAMP = 12, +}; + +static s16 gEvolvingHarmonic[64]; + +static void GenerateEvolvingHarmonic() { + static bool initialized = false; + if (!initialized) { + gEvolvingHarmonic[62] = 8191; + gEvolvingHarmonic[63] = 16383; + initialized = true; + } + + u32 prev2 = (u32)gEvolvingHarmonic[62]; + u32 prev1 = (u32)gEvolvingHarmonic[63]; + + for (int i = 0; i < 64; i += 2) { + u32 cur = (u32)gEvolvingHarmonic[i]; + gEvolvingHarmonic[i] = (s16)((s32)(prev2 * prev1 - (cur << 16)) >> 16); + prev2 = prev1; + prev1 = cur; + + cur = (u32)gEvolvingHarmonic[i + 1]; + gEvolvingHarmonic[i + 1] = (s16)((s32)(2u * (prev2 * prev1 + (cur << 16))) >> 16); + prev2 = prev1; + prev1 = cur; + } +} + + +static void RenderOscChannel( + JASDsp::TChannel& channel, + ChannelAuxData& channelAux, + OutputSubframe& subframe) { + if (channel.mResetFlag) + ResetChannel(channel, channelAux); + + const u32 pitch = channel.mPitch; + DspSubframe buf = {}; + const auto oscType = static_cast(channel.mBytesPerBlock); + + switch (oscType) { + case OscType::SQUARE_WAVE_PW_50: { + std::generate(buf.begin(), buf.end(), [&] { + f32 s = channelAux.oscPhase < 0x8000u ? 0.5f : -0.5f; + channelAux.oscPhase += pitch >> 1; + return s; + }); + break; + } + case OscType::SQUARE_WAVE_PW_25: { + std::generate(buf.begin(), buf.end(), [&] { + f32 s = channelAux.oscPhase < 0x4000u ? 0.5f : -0.5f; + channelAux.oscPhase += pitch >> 1; + return s; + }); + break; + } + case OscType::SAW_WAVE: + case OscType::EVOLVING_RAMP: { + std::generate(buf.begin(), buf.end(), [&] { + f32 s = (f32)(s16)channelAux.oscPhase / 32768.0f; + channelAux.oscPhase += pitch >> 1; + return s; + }); + break; + } + case OscType::SINE_WAVE: + case OscType::SINE_WAVE_VAR_STEP: { + std::generate(buf.begin(), buf.end(), [&] { + f32 s = sinf((f32)channelAux.oscPhase * (2.0f * M_PI / 65536.0f)) * 0.5f; + channelAux.oscPhase += pitch >> 1; + return s; + }); + break; + } + case OscType::TRIANGLE_WAVE: { + std::generate(buf.begin(), buf.end(), [&] { + f32 s = 0.5f - fabsf((f32)(s16)channelAux.oscPhase / 32768.0f); + channelAux.oscPhase += pitch >> 1; + return s; + }); + break; + } + case OscType::EVOLVING_HARMONIC: { + std::generate(buf.begin(), buf.end(), [&] { + f32 s = gEvolvingHarmonic[channelAux.oscPhase >> 10] / 32768.0f; + channelAux.oscPhase += pitch >> 1; + return s; + }); + break; + } + default: + DuskLog.error("RenderOscChannel: unimplemented oscillator type {}", channel.mBytesPerBlock); + break; + } + + auto samples = std::span(buf).subspan(0, DSP_SUBFRAME_SIZE); + RenderOutputChannel(channel, channelAux, OutputChannel::LEFT, samples, subframe); + RenderOutputChannel(channel, channelAux, OutputChannel::RIGHT, samples, subframe); +} + + void dusk::audio::DspRender(OutputSubframe& subframe) { ZoneScoped; if (DumpAudio != sDumpWasActive) { @@ -152,6 +275,8 @@ void dusk::audio::DspRender(OutputSubframe& subframe) { } } + GenerateEvolvingHarmonic(); + std::span channels(JASDsp::CH_BUF, DSP_CHANNELS); DspSubframe reverbInputL = {}; @@ -174,17 +299,14 @@ void dusk::audio::DspRender(OutputSubframe& subframe) { channel.mIsFinished = true; continue; } - else if (channel.mWaveAramAddress == 0) { - // I think these are oscillator channels? Not backed by audio. - // No idea how to implement these yet, so skip them. - channel.mIsFinished = true; - continue; - } - - ValidateChannel(channel); OutputSubframe channelSubframe = {}; - RenderChannel(channel, channelAux, channelSubframe); + if (channel.mWaveAramAddress == 0) { + RenderOscChannel(channel, channelAux, channelSubframe); + } else { + ValidateChannel(channel); + RenderChannel(channel, channelAux, channelSubframe); + } if (EnableReverb) { // scale the input to the reverb rather than using wet/dry on the output. diff --git a/src/dusk/audio/DuskDsp.hpp b/src/dusk/audio/DuskDsp.hpp index 3ca90d6311..8000e627a1 100644 --- a/src/dusk/audio/DuskDsp.hpp +++ b/src/dusk/audio/DuskDsp.hpp @@ -53,6 +53,9 @@ namespace dusk::audio { // last consumed sample from decodeBuf s16 resamplePrev; + // phase of oscillator channels + u16 oscPhase; + // low pass previous state f32 prev_lp_out; // out[n-1] f32 prev_lp_in; // in[n-1] diff --git a/src/dusk/config.cpp b/src/dusk/config.cpp index c377d1ba54..176967359d 100644 --- a/src/dusk/config.cpp +++ b/src/dusk/config.cpp @@ -10,7 +10,7 @@ #include #include -#include "dusk/dusk.h" +#include "dusk/main.h" using namespace dusk::config; @@ -24,7 +24,7 @@ static absl::flat_hash_map RegisteredConfigVar static bool RegistrationDone = false; static std::string GetConfigJsonPath() { - return fmt::format("{}{}", configPath, ConfigFileName); + return (dusk::ConfigPath / ConfigFileName).string(); } ConfigVarBase::ConfigVarBase(const char* name, const ConfigImplBase* impl) : name(name), registered(false), layer(ConfigVarLayer::Default), impl(impl) { diff --git a/src/dusk/crash_reporting.cpp b/src/dusk/crash_reporting.cpp index 73f432e418..0499f0d6a1 100644 --- a/src/dusk/crash_reporting.cpp +++ b/src/dusk/crash_reporting.cpp @@ -3,6 +3,7 @@ #include "dusk/app_info.hpp" #include "dusk/dusk.h" #include "dusk/logging.h" +#include "dusk/main.h" #include "dusk/settings.h" #include "version.h" @@ -66,7 +67,7 @@ std::string GetReleaseName() { } std::filesystem::path GetSentryDatabasePath() { - return std::filesystem::path(configPath) / "sentry"; + return dusk::ConfigPath / "sentry"; } std::filesystem::path GetLogAttachmentPath() { diff --git a/src/dusk/discord_presence.cpp b/src/dusk/discord_presence.cpp new file mode 100644 index 0000000000..3b12075c8b --- /dev/null +++ b/src/dusk/discord_presence.cpp @@ -0,0 +1,122 @@ +#ifdef DUSK_DISCORD_RPC + +#include "dusk/discord_presence.hpp" +#include "dusk/logging.h" +#include "dusk/main.h" +#include "dusk/map_loader_definitions.h" +#include "d/d_com_inf_game.h" +#include "discord_rpc.h" +#include "fmt/format.h" + +#include +#include +#include + +namespace dusk { +namespace discord { + +static int64_t g_startTime = 0; +static bool g_initialized = false; +static const char* APPLICATION_ID = "1495632471994405035"; + +static void OnReady(const DiscordUser* user) { + DuskLog.info("Discord: Connected as {}", user->username); +} + +static void OnDisconnected(int errorCode, const char* message) { + DuskLog.warn("Discord: Disconnected ({}: {})", errorCode, message); +} + +static void OnError(int errorCode, const char* message) { + DuskLog.warn("Discord: Error ({}: {})", errorCode, message); +} + +static const char* LookupMapName(const char* mapFile) { + if (!mapFile || mapFile[0] == '\0') return nullptr; + for (const auto& region : gameRegions) { + for (const auto& map : region.maps) { + if (map.mapFile && strcmp(mapFile, map.mapFile) == 0) { + return map.mapName; + } + } + } + return nullptr; +} + +void Initialize() { + g_startTime = static_cast( + std::chrono::duration_cast( + std::chrono::system_clock::now().time_since_epoch() + ).count() + ); + + DiscordEventHandlers handlers{}; + handlers.ready = OnReady; + handlers.disconnected = OnDisconnected; + handlers.errored = OnError; + Discord_Initialize(APPLICATION_ID, &handlers, 0, nullptr); + g_initialized = true; + + DuskLog.info("Discord Rich Presence initialized"); +} + +void RunCallbacks() { + if (!g_initialized) return; + Discord_RunCallbacks(); +} + +void UpdatePresence() { + if (!g_initialized) return; + + static auto lastUpdate = std::chrono::steady_clock::time_point{}; + const auto now = std::chrono::steady_clock::now(); + if (now - lastUpdate < std::chrono::seconds(15)) return; + lastUpdate = now; + + static std::string detailsBuf; + static std::string stateBuf; + + DiscordRichPresence presence{}; + presence.startTimestamp = g_startTime; + presence.largeImageKey = "icon"; + presence.largeImageText = "Dusk"; + + if (dusk::IsGameLaunched) { + const char* stageName = dComIfGp_getLastPlayStageName(); + + // stageName is empty until a room is actually entered + if (stageName[0] != '\0') { + const char* locationName = LookupMapName(stageName); + + if (locationName) { + detailsBuf = locationName; + } + else { + detailsBuf = "Twilight Princess"; + } + + presence.details = detailsBuf.c_str(); + + stateBuf = fmt::format(FMT_STRING("{}/{} \u2665 | {} Rupees"), + dComIfGs_getLife() / 4, dComIfGs_getMaxLife() / 5, dComIfGs_getRupee()); + + presence.state = stateBuf.c_str(); + } + } + + Discord_UpdatePresence(&presence); + DuskLog.debug("Discord Rich Presence sent"); +} + +void Shutdown() { + if (!g_initialized) return; + Discord_ClearPresence(); + Discord_Shutdown(); + g_initialized = false; + DuskLog.info("Discord Rich Presence shut down"); +} + +} // namespace discord +} // namespace dusk + +#endif // DUSK_DISCORD_RPC diff --git a/src/dusk/file_select.cpp b/src/dusk/file_select.cpp new file mode 100644 index 0000000000..fcda233c56 --- /dev/null +++ b/src/dusk/file_select.cpp @@ -0,0 +1,91 @@ +#include "file_select.hpp" + +#include + +#include +#include + +#if defined(__APPLE__) +#include +#endif + +#if defined(__APPLE__) && TARGET_OS_IOS && !TARGET_OS_MACCATALYST +#define USE_IOS_DIALOG 1 +#include "ios/FileSelectDialog.h" +#else +#define USE_IOS_DIALOG 0 +#endif + +namespace dusk { +namespace { + +#if USE_IOS_DIALOG +struct IOSDialogCallbackState { + FileCallback callback; + void* userdata; +}; + +void onIOSDialogFinished(void* userdata, const char* path, const char* error) { + std::unique_ptr state(static_cast(userdata)); + + if (error != nullptr) { + state->callback(state->userdata, nullptr, error); + return; + } + + if (path == nullptr) { + state->callback(state->userdata, nullptr, nullptr); + return; + } + + state->callback(state->userdata, path, nullptr); +} +#else +struct SDLDialogCallbackState { + FileCallback callback; + void* userdata; +}; + +void onSDLDialogFinished(void* userdata, const char* const* filelist, [[maybe_unused]] int filter) { + std::unique_ptr state(static_cast(userdata)); + + if (filelist == nullptr) { + state->callback(state->userdata, nullptr, SDL_GetError()); + return; + } + + if (filelist[0] == nullptr) { + state->callback(state->userdata, nullptr, nullptr); + return; + } + + state->callback(state->userdata, filelist[0], nullptr); +} +#endif + +} // namespace + +void ShowFileSelect(FileCallback callback, void* userdata, SDL_Window* window, + const SDL_DialogFileFilter* filters, int nfilters, const char* default_location, + bool allow_many) { + if (callback == nullptr) { + return; + } + +#if USE_IOS_DIALOG + auto state = std::make_unique(); + state->callback = callback; + state->userdata = userdata; + + Dusk_iOS_ShowFileSelect(&onIOSDialogFinished, state.release(), window, filters, nfilters, + default_location, allow_many); +#else + auto state = std::make_unique(); + state->callback = callback; + state->userdata = userdata; + + SDL_ShowOpenFileDialog(&onSDLDialogFinished, state.release(), window, filters, nfilters, + default_location, allow_many); +#endif +} +} // namespace dusk diff --git a/src/dusk/file_select.hpp b/src/dusk/file_select.hpp new file mode 100644 index 0000000000..175c5aa086 --- /dev/null +++ b/src/dusk/file_select.hpp @@ -0,0 +1,15 @@ +#pragma once + +#include + +struct SDL_Window; + +namespace dusk { + +using FileCallback = void (*)(void* userdata, const char* path, const char* error); + +void ShowFileSelect(FileCallback callback, void* userdata, SDL_Window* window, + const SDL_DialogFileFilter* filters, int nfilters, const char* default_location, + bool allow_many); + +} // namespace dusk diff --git a/src/dusk/frame_interpolation.cpp b/src/dusk/frame_interpolation.cpp index 0fa7d036e0..f3f8a842e1 100644 --- a/src/dusk/frame_interpolation.cpp +++ b/src/dusk/frame_interpolation.cpp @@ -1,91 +1,68 @@ -#include "f_op/f_op_camera_mng.h" #include "dusk/frame_interpolation.h" -#include -#include -#include #include -#include -#include +#include "mtx.h" +#include "f_op/f_op_camera_mng.h" +#include "m_Do/m_Do_graphic.h" namespace { -enum class Op : uint8_t { - OpenChild, - FinalMtx, -}; - -struct Label { - const void* key = nullptr; - int32_t id = 0; - - bool operator==(const Label& other) const { - return key == other.key && id == other.id; - } -}; - -struct Data { - Label child_label{}; - size_t child_index = 0; - Mtx matrix{}; - const Mtx* dest = nullptr; - uint64_t stable_tag = 0; -}; - -struct Path; - -struct ChildBucket { - Label label{}; - std::vector> nodes; -}; - -struct OpBucket { - Op op = Op::OpenChild; - std::vector values; -}; - -struct Path { - std::vector children; - std::vector ops; - std::vector> items; - Label draw_scope{}; - uint32_t simple_shadow_pair_seq = 0; -}; struct Recording { - Path root; + std::unordered_map matrix_values; }; -struct MatrixValue { - Mtx value; -}; - -using FinalMtxLookup = std::unordered_map; -using FinalMtxLookupTagged = std::unordered_map; - bool s_initialized = false; bool g_enabled = false; bool g_recording = false; bool g_interpolating = false; bool g_sync_presentation = false; -uint32_t g_presentation_counter = 0; float g_step = 0.0f; -uint32_t g_pending_presentation_ui_ticks = 0; -uint32_t g_current_presentation_ui_ticks = 0; +bool g_is_sim_frame = false; +bool g_ui_tick_pending = false; Recording g_current_recording; Recording g_previous_recording; -std::vector g_current_path; -std::unordered_map g_replacements; +std::unordered_map g_replacements; -inline void copy_matrix(const Mtx src, Mtx dst) { - MTXCopy(src, dst); -} +struct CameraSnapshot { + cXyz eye{}; + cXyz center{}; + cXyz up{}; + s16 bank{}; + f32 fovy{}; + f32 aspect{}; + f32 near_{}; + f32 far_{}; + bool wideZoom{}; + bool valid{}; +}; -inline void concat_matrix(const Mtx lhs, const Mtx rhs, Mtx out) { - MTXConcat(lhs, rhs, out); +CameraSnapshot s_cam_prev{}; +CameraSnapshot s_cam_curr{}; + +view_class s_presentation_view_backup{}; +int s_presentation_depth = 0; + +struct InterpolationCallBackWork { + dusk::frame_interp::InterpolationCallBack pCallBack; + void* pUserWork; +}; + +std::vector s_interpolationCallBackWork; + +void copy_view_to_snap(CameraSnapshot* dst, const view_class& v) { + dst->eye = v.lookat.eye; + dst->center = v.lookat.center; + dst->up = v.lookat.up; + dst->bank = v.bank; + dst->fovy = v.fovy; + dst->aspect = v.aspect; + dst->near_ = v.near_; + dst->far_ = v.far_; + dst->valid = true; } inline void lerp_matrix(Mtx out, const Mtx lhs, const Mtx rhs, float step) { @@ -104,6 +81,12 @@ inline void lerp_xyz(cXyz* out, const cXyz& lhs, const cXyz& rhs, float step) { out->z = lhs.z * old_weight + rhs.z * step; } +static s16 lerp_bank(s16 a, s16 b, f32 t) { + const f32 ra = S2RAD(a); + const f32 d = remainderf(S2RAD(b) - ra, 2.0f * static_cast(M_PI)); + return cAngle::Radian_to_SAngle(ra + d * t); +} + inline bool matrix_differs(const Mtx lhs, const Mtx rhs, float epsilon = 0.0001f) { for (size_t row = 0; row < 3; ++row) { for (size_t col = 0; col < 4; ++col) { @@ -115,162 +98,22 @@ inline bool matrix_differs(const Mtx lhs, const Mtx rhs, float epsilon = 0.0001f return false; } -Data& append_op(Op op) { - auto& items = g_current_path.back()->items; - auto& buckets = g_current_path.back()->ops; - auto it = std::find_if(buckets.begin(), buckets.end(), - [op](const OpBucket& bucket) { return bucket.op == op; }); - if (it == buckets.end()) { - buckets.push_back({op, {}}); - it = buckets.end() - 1; - } - items.emplace_back(op, it->values.size()); - return it->values.emplace_back(); -} - -const Data* find_matching_data(const Path& path, Op op, size_t index) { - auto it = std::find_if(path.ops.begin(), path.ops.end(), - [op](const OpBucket& bucket) { return bucket.op == op; }); - if (it == path.ops.end() || index >= it->values.size()) { - return nullptr; - } - return &it->values[index]; -} - -const OpBucket* find_op_bucket(const Path& path, Op op) { - auto it = std::find_if(path.ops.begin(), path.ops.end(), - [op](const OpBucket& bucket) { return bucket.op == op; }); - if (it == path.ops.end()) { - return nullptr; - } - return &*it; -} - -void build_final_mtx_lookups(const Path& path, FinalMtxLookup& dest_lookup, FinalMtxLookupTagged& tag_lookup) { - dest_lookup.clear(); - tag_lookup.clear(); - - const OpBucket* bucket = find_op_bucket(path, Op::FinalMtx); - if (bucket == nullptr) { - return; - } - - for (const Data& data : bucket->values) { - if (data.dest != nullptr) { - dest_lookup[data.dest] = &data; - } - if (data.stable_tag != 0) { - tag_lookup[data.stable_tag] = &data; - } - } -} - -const Data* find_matching_final_mtx(const FinalMtxLookup& lookup, const Data& new_data) { - if (new_data.dest == nullptr) { - return nullptr; - } - - auto it = lookup.find(new_data.dest); - if (it == lookup.end()) { - return nullptr; - } - return it->second; -} - -ChildBucket& get_child_bucket(Path& path, const Label& label) { - auto it = std::find_if(path.children.begin(), path.children.end(), - [&label](const ChildBucket& bucket) { return bucket.label == label; }); - if (it == path.children.end()) { - path.children.push_back({}); - it = path.children.end() - 1; - it->label = label; - } - return *it; -} - -const ChildBucket* find_child_bucket(const Path& path, const Label& label) { - auto it = std::find_if(path.children.begin(), path.children.end(), - [&label](const ChildBucket& bucket) { return bucket.label == label; }); - if (it == path.children.end()) { - return nullptr; - } - return &*it; -} - -void store_replacement(const Data& old_data, const Data& new_data, float step) { - if (new_data.dest == nullptr) { - return; - } - - auto& replacement = g_replacements[new_data.dest]; - lerp_matrix(replacement.value, old_data.matrix, new_data.matrix, step); -} - -void interpolate_branch(const Path& old_path, const Path& new_path, float step) { - FinalMtxLookup old_final_mtx_lookup; - FinalMtxLookupTagged old_final_mtx_lookup_tagged; - build_final_mtx_lookups(old_path, old_final_mtx_lookup, old_final_mtx_lookup_tagged); - - for (const auto& item : new_path.items) { - const Op op = item.first; - const size_t index = item.second; - const Data* new_data = find_matching_data(new_path, op, index); - if (new_data == nullptr) { - continue; - } - - if (op == Op::OpenChild) { - const ChildBucket* new_children = find_child_bucket(new_path, new_data->child_label); - if (new_children == nullptr || new_data->child_index >= new_children->nodes.size()) - { - continue; - } - - const Path& new_child = *new_children->nodes[new_data->child_index]; - const ChildBucket* old_children = find_child_bucket(old_path, new_data->child_label); - if (old_children != nullptr && new_data->child_index < old_children->nodes.size()) - { - interpolate_branch(*old_children->nodes[new_data->child_index], new_child, step); - } else { - interpolate_branch(new_child, new_child, step); - } - continue; - } - - const Data* indexed_old_data = find_matching_data(old_path, op, index); - const Data* old_data = nullptr; - if (op == Op::FinalMtx) { - if (new_data->stable_tag != 0) { - const auto it = old_final_mtx_lookup_tagged.find(new_data->stable_tag); - old_data = it != old_final_mtx_lookup_tagged.end() ? it->second : nullptr; - } else { - old_data = find_matching_final_mtx(old_final_mtx_lookup, *new_data); - } - } else { - old_data = indexed_old_data; - } - if (op == Op::FinalMtx) { - store_replacement(old_data != nullptr ? *old_data : *new_data, *new_data, step); - } - } -} - const Mtx* resolve_replacement(const Mtx* source, Mtx* scratch) { if (!g_interpolating || source == nullptr || dusk::frame_interp::presentation_sync_active()) { return source; } - auto it = g_replacements.find(source); + auto it = g_replacements.find(reinterpret_cast(source)); if (it == g_replacements.end()) { return source; } - copy_matrix(it->second.value, *scratch); + MTXCopy(it->second, *scratch); return scratch; } bool has_recording_data(const Recording& recording) { - return !recording.root.items.empty() || !recording.root.children.empty(); + return !recording.matrix_values.empty(); } void clear_replacements() { @@ -281,51 +124,80 @@ void clear_replacements() { namespace dusk::frame_interp { void ensure_initialized() { - g_enabled = getSettings().game.enableFrameInterpolation; s_initialized = true; } +void begin_sim_tick() { + ensure_initialized(); + if (!g_enabled) { + return; + } + + s_interpolationCallBackWork.clear(); + s_cam_prev = std::move(s_cam_curr); +} + +void begin_frame(bool enabled, bool is_sim_frame, float step) { + g_enabled = enabled; + g_is_sim_frame = is_sim_frame; + g_step = std::clamp(step, 0.0f, 1.0f); +} + +bool is_enabled() { + return g_enabled; +} + +bool is_sim_frame() { + return g_is_sim_frame; +} + void begin_record() { ensure_initialized(); + if (!g_enabled) { g_interpolating = false; g_sync_presentation = false; g_previous_recording = {}; g_current_recording = {}; - g_current_path.clear(); clear_replacements(); + s_cam_prev.valid = false; + s_cam_curr.valid = false; return; } g_sync_presentation = false; g_previous_recording = std::move(g_current_recording); g_current_recording = {}; - g_current_path.clear(); - g_current_path.push_back(&g_current_recording.root); g_recording = true; g_interpolating = false; clear_replacements(); + + ::camera_process_class* cam = dComIfGp_getCamera(0); + if (cam == nullptr) { + s_cam_prev.valid = false; + s_cam_curr.valid = false; + return; + } } void end_record() { g_recording = false; } -void interpolate(float step) { +void interpolate() { ensure_initialized(); clear_replacements(); - g_step = std::clamp(step, 0.0f, 1.0f); g_interpolating = g_enabled && !g_recording && !g_sync_presentation && has_recording_data(g_current_recording); if (!g_interpolating) { return; } - const Path& old_root = has_recording_data(g_previous_recording) ? g_previous_recording.root : g_current_recording.root; - interpolate_branch(old_root, g_current_recording.root, g_step); -} - -void notify_presentation_frame() { - ensure_initialized(); - ++g_presentation_counter; + for (auto const& old : g_previous_recording.matrix_values) { + if (auto it = g_current_recording.matrix_values.find(old.first); + it != g_current_recording.matrix_values.end()) + { + lerp_matrix(g_replacements[old.first], old.second, it->second, g_step); + } + } } void request_presentation_sync() { @@ -348,92 +220,40 @@ float get_interpolation_step() { return presentation_sync_active() ? 1.0f : g_step; } -void notify_sim_tick_complete() { +void set_ui_tick_pending(bool value) { + if (g_ui_tick_pending == value) { return; } + g_ui_tick_pending = value; +} + +bool get_ui_tick_pending() { ensure_initialized(); - g_pending_presentation_ui_ticks++; + return g_enabled ? g_ui_tick_pending : true; } -uint32_t begin_presentation_ui_pass() { - ensure_initialized(); - g_current_presentation_ui_ticks = g_pending_presentation_ui_ticks; - g_pending_presentation_ui_ticks = 0; - return g_current_presentation_ui_ticks; -} - -uint32_t get_presentation_ui_advance_ticks() { - if (!s_initialized) { - return 0; - } - if (!g_enabled) { - return 1; - } - return g_current_presentation_ui_ticks; -} - -void end_presentation_ui_pass() { - if (!s_initialized) { - return; - } - g_current_presentation_ui_ticks = 0; -} - -void open_child(const void* key, int32_t id) { - if (!s_initialized || !g_recording) { +void record_final_mtx(Mtx m, const void* key) { + if (!s_initialized || !g_recording || m == nullptr) { return; } - Label label{key, id}; - auto& siblings = get_child_bucket(*g_current_path.back(), label).nodes; - Data& data = append_op(Op::OpenChild); - data.child_label = label; - data.child_index = siblings.size(); - siblings.emplace_back(std::make_unique()); - Path* const child = siblings.back().get(); - child->draw_scope = label; - g_current_path.push_back(child); + auto& it = g_current_recording.matrix_values[reinterpret_cast(key)]; + MTXCopy(m, it); } -void close_child() { - if (!s_initialized || !g_recording || g_current_path.size() <= 1) { - return; - } - - g_current_path.pop_back(); +void record_final_mtx(Mtx m) { + record_final_mtx(m, m); } -void record_final_mtx_raw(const Mtx* dest, const Mtx src) { - if (!s_initialized || !g_recording || dest == nullptr) { - return; - } - - Data& data = append_op(Op::FinalMtx); - data.dest = dest; - data.stable_tag = 0; - copy_matrix(src, data.matrix); -} - -void record_final_mtx_raw_tagged(const Mtx* dest, const Mtx src, uint64_t stable_tag) { - if (!s_initialized || !g_recording || dest == nullptr) { - return; - } - - Data& data = append_op(Op::FinalMtx); - data.dest = dest; - data.stable_tag = stable_tag; - copy_matrix(src, data.matrix); -} - -bool lookup_replacement(const void* source, Mtx out) { - if (presentation_sync_active() || !g_interpolating || source == nullptr) { +bool lookup_replacement(const void* key, Mtx out) { + if (presentation_sync_active() || !g_interpolating || key == nullptr) { return false; } - auto it = g_replacements.find(reinterpret_cast(source)); + auto it = g_replacements.find(reinterpret_cast(key)); if (it == g_replacements.end()) { return false; } - copy_matrix(it->second.value, out); + MTXCopy(it->second, out); return true; } @@ -450,103 +270,170 @@ bool lookup_concat_replacement(const void* lhs, const void* rhs, Mtx out) { return false; } - concat_matrix(*resolved_lhs, *resolved_rhs, out); + MTXConcat(*resolved_lhs, *resolved_rhs, out); return true; } -// TODO: Is there already a built-in function for this? -void camera_eye_from_view_mtx(MtxP view_mtx, cXyz* o_eye) { - o_eye->x = -(view_mtx[0][0] * view_mtx[0][3] + view_mtx[1][0] * view_mtx[1][3] + view_mtx[2][0] * view_mtx[2][3]); - o_eye->y = -(view_mtx[0][1] * view_mtx[0][3] + view_mtx[1][1] * view_mtx[1][3] + view_mtx[2][1] * view_mtx[2][3]); - o_eye->z = -(view_mtx[0][2] * view_mtx[0][3] + view_mtx[1][2] * view_mtx[1][3] + view_mtx[2][2] * view_mtx[2][3]); -} - -namespace { -struct CamSnap { - cXyz eye{}; - cXyz center{}; - cXyz up{}; - s16 bank{}; - f32 fovy{}; - bool valid{}; -}; - -CamSnap s_star_prev{}; -CamSnap s_star_curr{}; - -static void copy_view_to_snap(CamSnap* dst, const view_class& v) { - dst->eye = v.lookat.eye; - dst->center = v.lookat.center; - dst->up = v.lookat.up; - dst->bank = v.bank; - dst->fovy = v.fovy; - dst->valid = true; -} - -static void billboard_base_from_view(MtxP view_mtx, MtxP o_cam_billboard_base) { - Mtx rot; - MTXCopy(view_mtx, rot); - rot[0][3] = rot[1][3] = rot[2][3] = 0.0f; - MTXInverse(rot, o_cam_billboard_base); -} -} // namespace - -void begin_record_camera() { - ::camera_process_class* cam = dComIfGp_getCamera(0); - if (cam == nullptr) { - return; - } - copy_view_to_snap(&s_star_prev, cam->view); -} - void record_camera(::camera_process_class* cam, int camera_id) { - if (!getSettings().game.enableFrameInterpolation || camera_id != 0 || cam == nullptr) { + if (!g_enabled || camera_id != 0 || cam == nullptr) { return; } - copy_view_to_snap(&s_star_curr, cam->view); + copy_view_to_snap(&s_cam_curr, cam->view); +#if WIDESCREEN_SUPPORT + s_cam_curr.wideZoom = mDoGph_gInf_c::isWideZoom(); +#endif } -bool build_star_view(Mtx o_view, Mtx o_cam_billboard_base, cXyz* o_anchor_eye, float* o_fovy) { - if (!getSettings().game.enableFrameInterpolation || !s_star_prev.valid || !s_star_curr.valid) { - return false; - } +void interp_view(::view_class* view) { + if (!g_enabled) + return; + + if (!s_cam_prev.valid || !s_cam_curr.valid) + return; const f32 step = get_interpolation_step(); cXyz eye; cXyz center; cXyz up; - lerp_xyz(&eye, s_star_prev.eye, s_star_curr.eye, step); - lerp_xyz(¢er, s_star_prev.center, s_star_curr.center, step); - lerp_xyz(&up, s_star_prev.up, s_star_curr.up, step); + lerp_xyz(&eye, s_cam_prev.eye, s_cam_curr.eye, step); + lerp_xyz(¢er, s_cam_prev.center, s_cam_curr.center, step); + lerp_xyz(&up, s_cam_prev.up, s_cam_curr.up, step); if (!up.normalizeRS()) { - up = s_star_curr.up; + up = s_cam_curr.up; up.normalizeRS(); } - const f32 bank_rad = S2RAD(s_star_prev.bank) * (1.0f - step) + S2RAD(s_star_curr.bank) * step; - const s16 bank = cAngle::Radian_to_SAngle(bank_rad); + view->lookat.eye = eye; + view->lookat.center = center; + view->lookat.up = up; + view->bank = lerp_bank(s_cam_prev.bank, s_cam_curr.bank, step); + view->fovy = s_cam_prev.fovy + (s_cam_curr.fovy - s_cam_prev.fovy) * step; + view->aspect = s_cam_prev.aspect + (s_cam_curr.aspect - s_cam_prev.aspect) * step; + view->near_ = s_cam_prev.near_ + (s_cam_curr.near_ - s_cam_prev.near_) * step; + view->far_ = s_cam_prev.far_ + (s_cam_curr.far_ - s_cam_prev.far_) * step; - mDoMtx_lookAt(o_view, &eye, ¢er, &up, bank); - billboard_base_from_view(o_view, o_cam_billboard_base); - - *o_anchor_eye = eye; - *o_fovy = s_star_prev.fovy + (s_star_curr.fovy - s_star_prev.fovy) * step; - return true; + // FRAME INTERP TODO: It might be better if I rewired the game to not clear this flag until the + // next sim frame, but I don't care enough to right now +#if WIDESCREEN_SUPPORT + if (mDoGph_gInf_c::isWide() && !mDoGph_gInf_c::isWideZoom() && step >= 0.5f ? + s_cam_curr.wideZoom : + s_cam_prev.wideZoom) + { + mDoGph_gInf_c::onWideZoom(); + } +#endif } -uint64_t alloc_simple_shadow_pair_base() { - if (!s_initialized || !g_recording || g_current_path.size() <= 1) { - return 0; +static void run_interpolation_callbacks() { + for (size_t i = 0; i < s_interpolationCallBackWork.size(); i++) { + auto const& work = s_interpolationCallBackWork[i]; + work.pCallBack(g_is_sim_frame, work.pUserWork); + } +} + +void add_interpolation_callback(InterpolationCallBack pCallBack, void* pUserWork) { + if (!is_enabled() || s_presentation_depth > 0 || !g_is_sim_frame) + return; + + s_interpolationCallBackWork.emplace_back(pCallBack, pUserWork); +} + +void begin_presentation_camera() { + ensure_initialized(); + if (!g_enabled) { + return; + } + if (s_presentation_depth > 0) { + s_presentation_depth++; + return; + } + if (!s_cam_prev.valid || !s_cam_curr.valid) { + return; } - Path* const scope = g_current_path.back(); - const uint64_t h = static_cast(reinterpret_cast(scope->draw_scope.key)); - const uint32_t lo = scope->simple_shadow_pair_seq; - scope->simple_shadow_pair_seq += 2; - uint64_t tag0 = (h << 17) ^ (static_cast(lo) << 1u); - if (tag0 == 0) { - tag0 = 2; + view_class* const view = dComIfGd_getView(); + if (view == nullptr) { + return; + } + + std::memcpy(&s_presentation_view_backup, view, sizeof(view_class)); + interp_view(view); + + // FRAME INTERP TODO: Largely copied from d_camera's camera_draw function from this point, got any better ideas? + C_MTXPerspective(view->projMtx, view->fovy, view->aspect, view->near_, view->far_); + mDoMtx_lookAt(view->viewMtx, &view->lookat.eye, &view->lookat.center, &view->lookat.up, view->bank); +#if WIDESCREEN_SUPPORT + mDoGph_gInf_c::setWideZoomProjection(view->projMtx); +#endif + j3dSys.setViewMtx(view->viewMtx); + cMtx_inverse(view->viewMtx, view->invViewMtx); + + bool camera_attention_status = dComIfGp_getCameraAttentionStatus(0) & 0x80; + Z2GetAudience()->setAudioCamera(view->viewMtx, view->lookat.eye, view->lookat.center, view->fovy, view->aspect, camera_attention_status, 0, false); + + dBgS_GndChk gndchk; + gndchk.OnWaterGrp(); + gndchk.SetPos(&view->lookat.eye); + f32 cross = dComIfG_Bgsp().GroundCross(&gndchk); + if (cross != -G_CM3D_F_INF) { + if (dComIfG_Bgsp().ChkGrpInf(gndchk, 0x100)) { + mDoAud_getCameraMapInfo(6); + } else { + mDoAud_getCameraMapInfo(dComIfG_Bgsp().GetMtrlSndId(gndchk)); + } + mDoAud_setCameraGroupInfo(dComIfG_Bgsp().GetGrpSoundId(gndchk)); + Vec spDC; + spDC.x = view->lookat.eye.x; + spDC.y = cross; + spDC.z = view->lookat.eye.z; + Z2AudioMgr::getInterface()->setCameraPolygonPos(&spDC); + } else { + Z2AudioMgr::getInterface()->setCameraPolygonPos(nullptr); + } + + MTXCopy(view->viewMtx, view->viewMtxNoTrans); + view->viewMtxNoTrans[0][3] = 0.0f; + view->viewMtxNoTrans[1][3] = 0.0f; + view->viewMtxNoTrans[2][3] = 0.0f; + cMtx_concatProjView(view->projMtx, view->viewMtx, view->projViewMtx); + + f32 far_; + f32 var_f30; + if (dComIfGp_getCameraAttentionStatus(0) & 8) { + far_ = view->far_; + } else { +#if DEBUG + if (g_envHIO.mOther.mAdjustCullFar != 0) { + var_f30 = g_envHIO.mOther.mCullFarValue; + } else +#endif + { + var_f30 = dStage_stagInfo_GetCullPoint(dComIfGp_getStageStagInfo()); + } + far_ = var_f30; + } + + mDoLib_clipper::setup(view->fovy, view->aspect, view->near_, far_); + + // FRAME INTERP NOTE: Removed the call to offWideZoom that was here, it causes problems with presentation during cutscenes. + + s_presentation_depth = 1; + + run_interpolation_callbacks(); +} + +void end_presentation_camera() { + if (s_presentation_depth == 0) { + return; + } + s_presentation_depth--; + if (s_presentation_depth > 0) { + return; + } + + view_class* const view = dComIfGd_getView(); + if (view != nullptr) { + std::memcpy(view, &s_presentation_view_backup, sizeof(view_class)); } - return tag0; } } // namespace dusk::frame_interp diff --git a/src/dusk/game_clock.cpp b/src/dusk/game_clock.cpp new file mode 100644 index 0000000000..8b887f610c --- /dev/null +++ b/src/dusk/game_clock.cpp @@ -0,0 +1,103 @@ +#include "dusk/game_clock.h" + +#include +#include +#include +#include + +namespace dusk::game_clock { + +using clock = std::chrono::steady_clock; + +bool s_initialized = false; +clock::time_point s_previous_sample{}; +clock::time_point s_current_snapshot_time{}; + +std::unordered_map s_interval_last_sample; + +constexpr clock::duration kSimPeriodDuration = + std::chrono::duration_cast(std::chrono::duration(sim_pace())); +constexpr clock::duration kAbnormalGapResetThreshold = std::chrono::milliseconds(250); +constexpr int kMaxSimTicksPerFrame = 2; + +void ensure_initialized() { + if (s_initialized) { + return; + } + s_previous_sample = clock::now(); + s_current_snapshot_time = s_previous_sample; + s_initialized = true; +} + +void reset_frame_timer() { + s_previous_sample = clock::now(); + s_current_snapshot_time = s_previous_sample - kSimPeriodDuration; +} + +MainLoopPacer advance_main_loop() { + ensure_initialized(); + + const clock::time_point now = clock::now(); + const clock::duration frame_gap = now - s_previous_sample; + const float presentation_dt = std::chrono::duration(frame_gap).count(); + s_previous_sample = now; + + MainLoopPacer out{}; + out.presentation_dt_seconds = presentation_dt; + + const bool should_interpolate = dusk::getSettings().game.enableFrameInterpolation && + !dusk::getTransientSettings().skipFrameRateLimit; + out.is_interpolating = should_interpolate; + out.sim_pace = sim_pace(); + + if (!should_interpolate) { + s_current_snapshot_time = now; + out.sim_ticks_to_run = 1; + return out; + } + + if (frame_gap > kAbnormalGapResetThreshold) { + s_current_snapshot_time = now - kSimPeriodDuration; + out.sim_ticks_to_run = 0; + return out; + } + + int sim_ticks_to_run = 0; + clock::time_point projected_snapshot_time = s_current_snapshot_time; + const clock::time_point render_time = now - kSimPeriodDuration; + while (sim_ticks_to_run < kMaxSimTicksPerFrame && projected_snapshot_time < render_time) { + projected_snapshot_time += kSimPeriodDuration; + sim_ticks_to_run++; + } + out.sim_ticks_to_run = sim_ticks_to_run; + return out; +} + +void commit_sim_tick() { + ensure_initialized(); + s_current_snapshot_time += kSimPeriodDuration; +} + +float sample_interpolation_step() { + ensure_initialized(); + const float step = + std::chrono::duration(clock::now() - s_current_snapshot_time).count() / sim_pace(); + return std::clamp(step, 0.0f, 1.0f); +} + +float consume_interval(const void* consumer) { + ensure_initialized(); + const uintptr_t key = reinterpret_cast(consumer); + const clock::time_point now = clock::now(); + + float dt = ui_initial_dt(); + const auto it = s_interval_last_sample.find(key); + if (it != s_interval_last_sample.end()) { + dt = std::chrono::duration(now - it->second).count(); + dt = std::min(dt, ui_maximum_dt()); + } + s_interval_last_sample[key] = now; + return dt; +} + +} // namespace dusk::game_clock diff --git a/src/dusk/gyro.cpp b/src/dusk/gyro.cpp new file mode 100644 index 0000000000..5b6e880a1f --- /dev/null +++ b/src/dusk/gyro.cpp @@ -0,0 +1,123 @@ +#include "dusk/gyro.h" +#include "d/actor/d_a_alink.h" + +namespace dusk::gyro { +namespace { +constexpr s32 kRollgoalTableMaxOffset = 6500; +constexpr float kGyroEmaAlphaMin = 0.05f; +constexpr float kGyroEmaAlphaMax = 1.0f; + +bool s_sensor_enabled = false; +float s_smooth_gx = 0.0f; +float s_smooth_gy = 0.0f; +float s_smooth_gz = 0.0f; +float s_yaw_rad = 0.0f; +float s_pitch_rad = 0.0f; +float s_roll_rad = 0.0f; +s32 s_rollgoal_ax = 0; +s32 s_rollgoal_az = 0; + +void reset_filter_state() { + s_smooth_gx = s_smooth_gy = s_smooth_gz = 0.0f; + s_yaw_rad = s_pitch_rad = s_roll_rad = 0.0f; + s_rollgoal_ax = s_rollgoal_az = 0; +} + +float apply_deadband(float v, float deadband_rad_s) { + if (v > -deadband_rad_s && v < deadband_rad_s) { + return 0.0f; + } + return v; +} +} // namespace + +bool s_sensor_keep_alive = false; +bool get_sensor_keep_alive() { return s_sensor_keep_alive; } +void set_sensor_keep_alive(bool value) { s_sensor_keep_alive = value; } + +bool queryGyroAimContext() { + if (!static_cast(dusk::getSettings().game.enableGyroAim)) { + return false; + } + + daAlink_c* link = daAlink_getAlinkActorClass(); + if (link == nullptr) { + return false; + } + + return link->checkGyroAimContext() && dComIfGp_checkCameraAttentionStatus(link->field_0x317c, 0x10); +} + +void read(float dt) { + if (!s_sensor_keep_alive && !queryGyroAimContext()) { + if (s_sensor_enabled) { + PADSetSensorEnabled(PAD_CHAN0, PAD_SENSOR_GYRO, FALSE); + s_sensor_enabled = false; + } + reset_filter_state(); + return; + } + + if (!s_sensor_enabled) { + if (!PADHasSensor(PAD_CHAN0, PAD_SENSOR_GYRO)) { + return; + } + if (!PADSetSensorEnabled(PAD_CHAN0, PAD_SENSOR_GYRO, TRUE)) { + return; + } + s_sensor_enabled = true; + } + + f32 gyro[3]; + if (!PADGetSensorData(PAD_CHAN0, PAD_SENSOR_GYRO, gyro, 3)) { + return; + } + + const float smooth_alpha = kGyroEmaAlphaMax + dusk::getSettings().game.gyroSmoothing * (kGyroEmaAlphaMin - kGyroEmaAlphaMax); + const float deadband = dusk::getSettings().game.gyroDeadband; + + s_smooth_gx += smooth_alpha * (gyro[0] - s_smooth_gx); + s_smooth_gy += smooth_alpha * (gyro[1] - s_smooth_gy); + s_smooth_gz += smooth_alpha * (gyro[2] - s_smooth_gz); + + s_pitch_rad = -apply_deadband(s_smooth_gx, deadband) * dt * dusk::getSettings().game.gyroSensitivityX; + s_yaw_rad = apply_deadband(s_smooth_gy, deadband) * dt * dusk::getSettings().game.gyroSensitivityY; + s_roll_rad = apply_deadband(s_smooth_gz, deadband) * dt * dusk::getSettings().game.gyroSensitivityX; // GYRO NOTE: Exposing Z sensitivity seems unusual, so I'm just using X + + s_pitch_rad = dusk::getSettings().game.gyroInvertPitch ? -s_pitch_rad : s_pitch_rad; + s_yaw_rad = dusk::getSettings().game.gyroInvertYaw ? -s_yaw_rad : s_yaw_rad; + s_yaw_rad = dusk::getSettings().game.enableMirrorMode ? -s_yaw_rad : s_yaw_rad; +} + +void getAimDeltas(float& out_yaw, float& out_pitch) { + out_yaw = s_yaw_rad; + out_pitch = s_pitch_rad; +} + +void rollgoalTick(bool play_active, s16 camera_yaw) { + if (!play_active) { + reset_filter_state(); + return; + } + + float pitch_rad = -s_pitch_rad * dusk::getSettings().game.gyroSensitivityRollgoal; + float roll_rad = s_roll_rad * dusk::getSettings().game.gyroSensitivityRollgoal; + roll_rad = dusk::getSettings().game.enableMirrorMode ? -roll_rad : roll_rad; + + s_rollgoal_az += cM_rad2s(roll_rad); + cXyz in(roll_rad, 0.0f, pitch_rad); + cXyz out; + cMtx_YrotS(*calc_mtx, -camera_yaw); + MtxPosition(&in, &out); + + s_rollgoal_ax += cM_rad2s(out.z); + s_rollgoal_az += cM_rad2s(out.x); + s_rollgoal_ax = std::clamp(s_rollgoal_ax, -kRollgoalTableMaxOffset, kRollgoalTableMaxOffset); + s_rollgoal_az = std::clamp(s_rollgoal_az, -kRollgoalTableMaxOffset, kRollgoalTableMaxOffset); +} + +void rollgoalTableOffset(s16& out_ax, s16& out_az) { + out_ax = static_cast(s_rollgoal_ax); + out_az = static_cast(s_rollgoal_az); +} +} // namespace dusk::gyro diff --git a/src/dusk/gyro_aim.cpp b/src/dusk/gyro_aim.cpp deleted file mode 100644 index 48edd640b3..0000000000 --- a/src/dusk/gyro_aim.cpp +++ /dev/null @@ -1,87 +0,0 @@ -#include "dusk/gyro_aim.h" - -#include -#include "d/actor/d_a_alink.h" - -namespace dusk::gyro_aim { -namespace { -// TODO: Make deadband and smoothing configurable -constexpr float kDeadbandRadS = 0.04f; -constexpr float kSmoothAlpha = 0.35f; -bool s_sensor_enabled = false; -float s_smooth_gx = 0.0f; -float s_smooth_gy = 0.0f; -float s_pending_yaw_rad = 0.0f; -float s_pending_pitch_rad = 0.0f; - -void reset_filter_state() { - s_smooth_gx = s_smooth_gy = 0.0f; - s_pending_yaw_rad = s_pending_pitch_rad = 0.0f; -} - -float apply_deadband(float v) { - if (v > -kDeadbandRadS && v < kDeadbandRadS) { - return 0.0f; - } - return v; -} -} // namespace - -void read(float dt, bool context_active) { - if (!context_active || !static_cast(dusk::getSettings().game.enableGyroAim)) { - SDL_Gamepad* pad = SDL_GetGamepadFromPlayerIndex(0); - if (pad != nullptr && s_sensor_enabled) { - SDL_SetGamepadSensorEnabled(pad, SDL_SENSOR_GYRO, false); - s_sensor_enabled = false; - } - reset_filter_state(); - return; - } - - SDL_Gamepad* pad = SDL_GetGamepadFromPlayerIndex(0); - if (pad == nullptr || !SDL_GamepadHasSensor(pad, SDL_SENSOR_GYRO)) { - return; - } - - if (!s_sensor_enabled) { - if (!SDL_SetGamepadSensorEnabled(pad, SDL_SENSOR_GYRO, true)) { - return; - } - s_sensor_enabled = true; - reset_filter_state(); - } - - float gyro[3]; - if (!SDL_GetGamepadSensorData(pad, SDL_SENSOR_GYRO, gyro, 3)) { - return; - } - - s_smooth_gx += kSmoothAlpha * (gyro[0] - s_smooth_gx); - s_smooth_gy += kSmoothAlpha * (gyro[1] - s_smooth_gy); - float yaw_rate = apply_deadband(s_smooth_gy); - float pitch_rate = apply_deadband(s_smooth_gx); - - s_pending_yaw_rad += yaw_rate * dt * dusk::getSettings().game.gyroAimSensitivityX; - s_pending_pitch_rad += pitch_rate * dt * dusk::getSettings().game.gyroAimSensitivityY; -} - -void consumeAimDeltas(float& out_yaw_rad, float& out_pitch_rad) { - out_yaw_rad = s_pending_yaw_rad; - out_pitch_rad = s_pending_pitch_rad; - s_pending_yaw_rad = 0.0f; - s_pending_pitch_rad = 0.0f; -} - -bool queryGyroAimItemContext() { - if (!static_cast(dusk::getSettings().game.enableGyroAim)) { - return false; - } - - daAlink_c* link = daAlink_getAlinkActorClass(); - if (link == nullptr) { - return false; - } - - return link->checkGyroAimItemContext() && dComIfGp_checkCameraAttentionStatus(link->field_0x317c, 0x10); -} -} // namespace dusk::gyro_aim diff --git a/src/dusk/imgui/ImGuiBloomWindow.cpp b/src/dusk/imgui/ImGuiBloomWindow.cpp new file mode 100644 index 0000000000..1741f82cd8 --- /dev/null +++ b/src/dusk/imgui/ImGuiBloomWindow.cpp @@ -0,0 +1,126 @@ +#include + +#include "imgui.h" + +#include "ImGuiBloomWindow.hpp" +#include "ImGuiMenuTools.hpp" +#include "m_Do/m_Do_graphic.h" + +namespace dusk { +namespace { +struct BloomOverride { + bool enabled = false; + bool bloomEnabled = true; + int mode = 0; + int point = 128; + int blureSize = 64; + int blureRatio = 128; + GXColor blendColor = {255, 255, 255, 255}; + GXColor monoColor = {0, 0, 0, 0}; +}; + +BloomOverride s_bloomOverride; + +void SyncFromCurrentBloom() { + mDoGph_gInf_c::bloom_c* bloom = mDoGph_gInf_c::getBloom(); + s_bloomOverride.bloomEnabled = bloom->getEnable() != 0; + s_bloomOverride.mode = bloom->mMode; + s_bloomOverride.point = bloom->getPoint(); + s_bloomOverride.blureSize = bloom->getBlureSize(); + s_bloomOverride.blureRatio = bloom->getBlureRatio(); + s_bloomOverride.blendColor = *bloom->getBlendColor(); + s_bloomOverride.monoColor = *bloom->getMonoColor(); +} + +u8 ClampToByte(int value) { + return static_cast(std::clamp(value, 0, 255)); +} + +void DrawColorEdit(const char* label, GXColor& color) { + float colorValue[4] = { + color.r / 255.0f, + color.g / 255.0f, + color.b / 255.0f, + color.a / 255.0f, + }; + if (ImGui::ColorEdit4(label, colorValue, ImGuiColorEditFlags_Uint8)) { + color.r = ClampToByte(static_cast(colorValue[0] * 255.0f + 0.5f)); + color.g = ClampToByte(static_cast(colorValue[1] * 255.0f + 0.5f)); + color.b = ClampToByte(static_cast(colorValue[2] * 255.0f + 0.5f)); + color.a = ClampToByte(static_cast(colorValue[3] * 255.0f + 0.5f)); + } +} +} // namespace + +void ApplyBloomOverride() { + if (!s_bloomOverride.enabled) { + return; + } + + mDoGph_gInf_c::bloom_c* bloom = mDoGph_gInf_c::getBloom(); + bloom->setEnable(s_bloomOverride.bloomEnabled ? 1 : 0); + bloom->setMode(static_cast(std::clamp(s_bloomOverride.mode, 0, 1))); + bloom->setPoint(ClampToByte(s_bloomOverride.point)); + bloom->setBlureSize(ClampToByte(s_bloomOverride.blureSize)); + bloom->setBlureRatio(ClampToByte(s_bloomOverride.blureRatio)); + bloom->setBlendColor(s_bloomOverride.blendColor); + bloom->setMonoColor(s_bloomOverride.monoColor); +} + +void DrawBloomWindow(bool& open) { + if (!open) { + return; + } + + if (!ImGui::Begin("Bloom", &open)) { + ImGui::End(); + return; + } + + bool copyCurrent = false; + if (ImGui::Checkbox("Override", &s_bloomOverride.enabled) && s_bloomOverride.enabled) { + copyCurrent = true; + } + + ImGui::SameLine(); + if (ImGui::Button("Sync with current")) { + copyCurrent = true; + } + + if (copyCurrent) { + SyncFromCurrentBloom(); + } + + ImGui::SeparatorText("Values"); + ImGui::Checkbox("Enabled", &s_bloomOverride.bloomEnabled); + ImGui::SliderInt("Mode", &s_bloomOverride.mode, 0, 1); + ImGui::SliderInt("Threshold", &s_bloomOverride.point, 0, 255); + ImGui::SliderInt("Blur Size", &s_bloomOverride.blureSize, 0, 255); + ImGui::SliderInt("Blur Ratio", &s_bloomOverride.blureRatio, 0, 255); + DrawColorEdit("Blend Color", s_bloomOverride.blendColor); + DrawColorEdit("Mono Color", s_bloomOverride.monoColor); + + ApplyBloomOverride(); + + ImGui::SeparatorText("Current"); + mDoGph_gInf_c::bloom_c* bloom = mDoGph_gInf_c::getBloom(); + GXColor blendColor = *bloom->getBlendColor(); + GXColor monoColor = *bloom->getMonoColor(); + ImGui::Text("Enabled: %d", bloom->getEnable()); + ImGui::Text("Mode: %d", bloom->mMode); + ImGui::Text("Threshold: %u", static_cast(bloom->getPoint())); + ImGui::Text("Blur Size: %u", static_cast(bloom->getBlureSize())); + ImGui::Text("Blur Ratio: %u", static_cast(bloom->getBlureRatio())); + ImGui::Text("Blend RGBA: %u, %u, %u, %u", static_cast(blendColor.r), + static_cast(blendColor.g), static_cast(blendColor.b), + static_cast(blendColor.a)); + ImGui::Text("Mono RGBA: %u, %u, %u, %u", static_cast(monoColor.r), + static_cast(monoColor.g), static_cast(monoColor.b), + static_cast(monoColor.a)); + ImGui::End(); +} + +void ImGuiMenuTools::ShowBloomWindow() { + DrawBloomWindow(m_showBloomWindow); +} +} // namespace dusk diff --git a/src/dusk/imgui/ImGuiBloomWindow.hpp b/src/dusk/imgui/ImGuiBloomWindow.hpp new file mode 100644 index 0000000000..58e644e96d --- /dev/null +++ b/src/dusk/imgui/ImGuiBloomWindow.hpp @@ -0,0 +1,6 @@ +#pragma once + +namespace dusk { +void DrawBloomWindow(bool& open); +void ApplyBloomOverride(); +} // namespace dusk diff --git a/src/dusk/imgui/ImGuiCameraOverlay.cpp b/src/dusk/imgui/ImGuiCameraOverlay.cpp index daaa2d6180..aa3d1ac093 100644 --- a/src/dusk/imgui/ImGuiCameraOverlay.cpp +++ b/src/dusk/imgui/ImGuiCameraOverlay.cpp @@ -49,9 +49,10 @@ namespace dusk { ImGui::SeparatorText("Free-look Data"); static float eyeYawDeg = 0.0f; - static float moveSpeed = 10000.0f; + static float moveSpeed = 5000.0f; static float rotSpeed = 5.0f; static cXyz freeLookPos = cXyz::Zero; + static bool freeLookActive = false; bool changed = false; @@ -91,7 +92,17 @@ namespace dusk { changed = true; } - if (changed) { + if (!freeLookActive && changed) { + freeLookPos += dCam->Center(); + freeLookActive = true; + } + + if (ImGui::IsKeyDown(ImGuiKey_R)) { + freeLookPos = cXyz::Zero; + freeLookActive = false; + } + + if (freeLookActive) { dCam->Reset(freeLookPos, freeLookPos + (frontDir * 100.0f)); } diff --git a/src/dusk/imgui/ImGuiConsole.cpp b/src/dusk/imgui/ImGuiConsole.cpp index 0cf3906d75..0b32fd5078 100644 --- a/src/dusk/imgui/ImGuiConsole.cpp +++ b/src/dusk/imgui/ImGuiConsole.cpp @@ -3,23 +3,25 @@ #include #include #include -#include -#include "fmt/format.h" +#define IMGUI_DEFINE_MATH_OPERATORS #include "imgui.h" #include +#include "fmt/format.h" #include "ImGuiConsole.hpp" - #include "JSystem/JUtility/JUTGamePad.h" +#include "SDL3/SDL_events.h" #include "SDL3/SDL_mouse.h" -#include "m_Do/m_Do_controller_pad.h" -#include "m_Do/m_Do_main.h" +#include "aurora/lib/window.hpp" +#include "dusk/audio/DuskAudioSystem.h" #include "dusk/config.hpp" +#include "dusk/dusk.h" +#include "dusk/frame_interpolation.h" #include "dusk/main.h" #include "dusk/settings.h" -#include "dusk/audio/DuskAudioSystem.h" -#include "dusk/dusk.h" +#include "m_Do/m_Do_controller_pad.h" +#include "m_Do/m_Do_main.h" #include "tracy/Tracy.hpp" #if _WIN32 @@ -30,6 +32,30 @@ using namespace std::string_literals; using namespace std::string_view_literals; +namespace { +ImVec2 TouchEventToScreenPos(const SDL_TouchFingerEvent& touch) { + const AuroraWindowSize size = aurora::window::get_window_size(); + return ImVec2{ + touch.x * static_cast(size.width), + touch.y * static_cast(size.height), + }; +} + +ImGuiWindow* FindDragScrollWindow(ImGuiWindow* window) { + while (window != nullptr) { + const bool canScrollX = window->ScrollMax.x > 0.0f; + const bool canScrollY = window->ScrollMax.y > 0.0f; + const bool canScrollWithMouse = (window->Flags & (ImGuiWindowFlags_NoScrollWithMouse | + ImGuiWindowFlags_NoMouseInputs)) == 0; + if ((canScrollX || canScrollY) && canScrollWithMouse) { + return window; + } + window = window->ParentWindow; + } + return nullptr; +} +} // namespace + namespace dusk { float ImGuiScale() { return 1.0f; } @@ -208,10 +234,55 @@ namespace dusk { ImGuiConsole::ImGuiConsole() {} + void ImGuiConsole::HandleSDLEvent(const SDL_Event& event) { + if (!IsGameLaunched) { + return; + } + + switch (event.type) { + case SDL_EVENT_FINGER_DOWN: + if (!m_touchTapActive) { + m_touchTapActive = true; + m_touchTapMoved = false; + m_touchTapFingerId = event.tfinger.fingerID; + m_touchTapStartPos = TouchEventToScreenPos(event.tfinger); + } + break; + case SDL_EVENT_FINGER_MOTION: + if (m_touchTapActive && m_touchTapFingerId == event.tfinger.fingerID) { + const auto currentPos = TouchEventToScreenPos(event.tfinger); + const auto delta = currentPos - m_touchTapStartPos; + if (ImLengthSqr(delta) > 144.0f) { + m_touchTapMoved = true; + } + } + break; + case SDL_EVENT_FINGER_UP: + if (m_touchTapActive && m_touchTapFingerId == event.tfinger.fingerID) { + const bool shouldToggle = + !m_touchTapMoved && (m_isHidden || !ImGui::GetIO().WantCaptureMouse); + m_touchTapActive = false; + m_touchTapMoved = false; + if (shouldToggle) { + m_isHidden = !m_isHidden; + getSettings().backend.duskMenuOpen.setValue(!m_isHidden); + Save(); + } + } + break; + case SDL_EVENT_FINGER_CANCELED: + m_touchTapActive = false; + m_touchTapMoved = false; + break; + default: + break; + } + } + void ImGuiConsole::UpdateSettings() { getTransientSettings().skipFrameRateLimit = getSettings().game.enableTurboKeybind && ImGui::IsKeyDown(ImGuiKey_Tab); - if (mDoMain::developmentMode == 1 && mDoCPd_c::getHoldL(PAD_1) && mDoCPd_c::getHoldR(PAD_1) && mDoCPd_c::getTrigY(PAD_1)) { + if (dusk::frame_interp::get_ui_tick_pending() && mDoMain::developmentMode == 1 && (mDoCPd_c::getHold(PAD_1) & (PAD_TRIGGER_R | PAD_TRIGGER_L)) == (PAD_TRIGGER_R | PAD_TRIGGER_L) && mDoCPd_c::getTrigY(PAD_1)) { getTransientSettings().moveLinkActive = !getTransientSettings().moveLinkActive; } if (mDoMain::developmentMode != 1) { @@ -234,34 +305,56 @@ namespace dusk { ImGuiMenuGame::ToggleFullscreen(); } - bool showMenu = !dusk::IsGameLaunched || !CheckMenuViewToggle(ImGuiKey_F1, m_isHidden); - - if (showMenu && ImGui::BeginMainMenuBar()) { - m_menuGame.draw(); - m_menuEnhancements.draw(); - m_menuTools.draw(); - - ImGui::SetCursorPosX(ImGui::GetWindowWidth() - 80.0f * ImGuiScale()); - ImGuiIO& io = ImGui::GetIO(); - ImGuiStringViewText(fmt::format(FMT_STRING("FPS: {:.2f}\n"), io.Framerate)); - - ImGui::EndMainMenuBar(); - } - if (!dusk::IsGameLaunched) { m_preLaunchWindow.draw(); } + m_isHidden = !getSettings().backend.duskMenuOpen; + bool showMenu = !dusk::IsGameLaunched || !CheckMenuViewToggle(ImGuiKey_F1, m_isHidden); + if (dusk::IsGameLaunched) { + const bool menuOpen = !m_isHidden; + if (getSettings().backend.duskMenuOpen != menuOpen) { + getSettings().backend.duskMenuOpen.setValue(menuOpen); + Save(); + } + } + + // The menu bar renders with ImGuiCol_WindowBg behind it. We just want ImGuiCol_MenuBarBg, + // so make the window bg fully transparent temporarily + ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(0.0f, 0.0f, 0.0f, 0.0f)); + if (showMenu && ImGui::BeginMainMenuBar()) { + m_menuGame.draw(); + m_menuTools.draw(); + + const auto fpsLabel = + fmt::format(FMT_STRING("FPS: {:.2f}\n"), ImGui::GetIO().Framerate); + const auto fpsSize = + ImGui::CalcTextSize(fpsLabel.data(), fpsLabel.data() + fpsLabel.size()); + ImGui::SetCursorPosX( + ImMax(ImGui::GetCursorPosX(), ImGui::GetWindowWidth() - + ImGui::GetStyle().DisplaySafeAreaPadding.x - + fpsSize.x - ImGui::GetStyle().ItemSpacing.x)); + ImGuiStringViewText(fpsLabel); + + ImGui::EndMainMenuBar(); + } + ImGui::PopStyleColor(); + if (!getSettings().backend.wasPresetChosen) { m_firstRunPreset.draw(); return; } if (dusk::IsGameLaunched && !m_isLaunchInitialized) { - m_toasts.emplace_back("Press F1 to toggle menu"s, 5.f); + m_toasts.emplace_back(ImGui::GetIO().MouseSource == ImGuiMouseSource_TouchScreen ? + "Tap to toggle menu"s : + "Press F1 to toggle menu"s, + 2.5f); m_isLaunchInitialized = true; } + UpdateDragScroll(); + m_menuGame.windowControllerConfig(); m_menuGame.windowInputViewer(); if (dusk::IsGameLaunched) { @@ -271,6 +364,7 @@ namespace dusk { m_menuTools.ShowHeapOverlay(); m_menuTools.ShowStubLog(); m_menuTools.ShowMapLoader(); + m_menuTools.ShowBloomWindow(); m_menuTools.ShowPlayerInfo(); m_menuTools.ShowAudioDebug(); m_menuTools.ShowSaveEditor(); @@ -278,10 +372,16 @@ namespace dusk { m_menuTools.ShowStateShare(); DuskDebugPad(); // temporary, remove later - // Only show cursor when menu or any windows are open - if (showMenu || ImGui::GetIO().MetricsRenderWindows > 0) { - ImGui::GetIO().ConfigFlags &= ~ImGuiConfigFlags_NoMouseCursorChange; - // Imgui will re-show cursor. + // Hide mouse cursor if the F1 menu is not open and the cursor is idle for 3 seconds. + ImGuiIO& io = ImGui::GetIO(); + if (showMenu) { + mouseHideTimer = 0.0f; + ImGui::GetIO().ConfigFlags &= ~ImGuiConfigFlags_NoMouseCursorChange; // Imgui will re-show cursor. + } else if (io.MouseDelta.x != 0.0f || io.MouseDelta.y != 0.0f) { + mouseHideTimer = 0.0f; + ImGui::GetIO().ConfigFlags &= ~ImGuiConfigFlags_NoMouseCursorChange; // Imgui will re-show cursor. + } else if (mouseHideTimer <= 3.0f) { + mouseHideTimer += ImGui::GetIO().DeltaTime; } else { ImGui::GetIO().ConfigFlags |= ImGuiConfigFlags_NoMouseCursorChange; SDL_HideCursor(); @@ -295,6 +395,57 @@ namespace dusk { ShowPipelineProgress(); } + void ImGuiConsole::UpdateDragScroll() { + ImGuiContext& g = *ImGui::GetCurrentContext(); + ImGuiIO& io = ImGui::GetIO(); + + if (io.MouseSource != ImGuiMouseSource_TouchScreen) { + m_dragScrollWindow = nullptr; + return; + } + + if (!ImGui::IsMouseDown(ImGuiMouseButton_Left)) { + m_dragScrollWindow = nullptr; + return; + } + + if (io.WantTextInput || (g.ActiveId != 0 && g.InputTextState.ID == g.ActiveId)) { + m_dragScrollWindow = nullptr; + return; + } + + if (!ImGui::IsMouseDragging(ImGuiMouseButton_Left, io.MouseDragThreshold)) { + return; + } + + if (m_dragScrollWindow == nullptr) { + ImGuiWindow* hoveredWindow = nullptr; + ImGuiWindow* hoveredWindowUnderMovingWindow = nullptr; + ImGui::FindHoveredWindowEx(io.MousePos, false, &hoveredWindow, + &hoveredWindowUnderMovingWindow); + m_dragScrollWindow = FindDragScrollWindow(hoveredWindow); + m_dragScrollLastMousePos = io.MousePos; + } + + if (m_dragScrollWindow == nullptr) { + return; + } + + const auto mouseDelta = io.MousePos - m_dragScrollLastMousePos; + m_dragScrollLastMousePos = io.MousePos; + + if (mouseDelta.x != 0.0f && m_dragScrollWindow->ScrollMax.x > 0.0f) { + ImGui::SetScrollX(m_dragScrollWindow, + ImClamp(m_dragScrollWindow->Scroll.x - mouseDelta.x, 0.0f, + m_dragScrollWindow->ScrollMax.x)); + } + if (mouseDelta.y != 0.0f && m_dragScrollWindow->ScrollMax.y > 0.0f) { + ImGui::SetScrollY(m_dragScrollWindow, + ImClamp(m_dragScrollWindow->Scroll.y - mouseDelta.y, 0.0f, + m_dragScrollWindow->ScrollMax.y)); + } + } + bool ImGuiConsole::CheckMenuViewToggle(ImGuiKey key, bool& active) { if (ImGui::IsKeyPressed(key)) { active = !active; diff --git a/src/dusk/imgui/ImGuiConsole.hpp b/src/dusk/imgui/ImGuiConsole.hpp index 0296dc24cc..de4e66c7d6 100644 --- a/src/dusk/imgui/ImGuiConsole.hpp +++ b/src/dusk/imgui/ImGuiConsole.hpp @@ -1,22 +1,27 @@ #ifndef DUSK_IMGUI_HPP #define DUSK_IMGUI_HPP -#include #include #include #include +#include +#include + #include "ImGuiFirstRunPreset.hpp" -#include "ImGuiMenuEnhancements.hpp" #include "ImGuiMenuGame.hpp" #include "ImGuiMenuTools.hpp" #include "ImGuiPreLaunchWindow.hpp" #include "imgui.h" +union SDL_Event; +struct ImGuiWindow; + namespace dusk { class ImGuiConsole { public: ImGuiConsole(); + void HandleSDLEvent(const SDL_Event& event); void UpdateSettings(); void PreDraw(); void PostDraw(); @@ -32,13 +37,20 @@ private: remain(duration) {} }; + float mouseHideTimer = 0.0f; + bool m_isHidden = true; bool m_isLaunchInitialized = false; + bool m_touchTapActive = false; + bool m_touchTapMoved = false; + SDL_FingerID m_touchTapFingerId = 0; + ImVec2 m_touchTapStartPos = {}; + ImGuiWindow* m_dragScrollWindow = nullptr; + ImVec2 m_dragScrollLastMousePos = {}; std::deque m_toasts; ImGuiFirstRunPreset m_firstRunPreset; ImGuiMenuGame m_menuGame; - ImGuiMenuEnhancements m_menuEnhancements; ImGuiPreLaunchWindow m_preLaunchWindow; // Keep always last @@ -46,6 +58,7 @@ private: void ShowToasts(); void ShowPipelineProgress(); + void UpdateDragScroll(); }; extern ImGuiConsole g_imguiConsole; diff --git a/src/dusk/imgui/ImGuiControllerOverlay.cpp b/src/dusk/imgui/ImGuiControllerOverlay.cpp index 8c24063375..dd0a15806c 100644 --- a/src/dusk/imgui/ImGuiControllerOverlay.cpp +++ b/src/dusk/imgui/ImGuiControllerOverlay.cpp @@ -5,7 +5,7 @@ #include "ImGuiConsole.hpp" #include "ImGuiMenuGame.hpp" -#include +#include namespace dusk { void ImGuiMenuGame::windowInputViewer() { @@ -260,31 +260,33 @@ namespace dusk { size.y = 130 * scale; ImGui::Dummy(size); - SDL_Gamepad* pad = SDL_GetGamepadFromPlayerIndex(0); - if (pad != nullptr && SDL_GamepadHasSensor(pad, SDL_SENSOR_GYRO)) { + if (PADHasSensor(PAD_1, PAD_SENSOR_GYRO) == TRUE) { ImGui::Separator(); - ImGui::TextUnformatted("Gyro"); + ImGui::Checkbox("Gyro Values", &m_showInputViewerGyro); + if (m_showInputViewerGyro) { + ImGui::TextUnformatted("Gyro"); - constexpr float kBarScale = 4.0f; - auto bar = [kBarScale](const char* label, float v) { - const float a = std::fabs(v); - const float t = std::min(1.f, a / kBarScale); - char overlay[32]; - snprintf(overlay, sizeof(overlay), "%s %+.3f", label, v); - ImGui::ProgressBar(t, ImVec2(-1, 0), overlay); - }; + constexpr float kBarScale = 4.0f; + auto bar = [kBarScale](const char* label, float v) { + const float a = std::fabs(v); + const float t = std::min(1.f, a / kBarScale); + char overlay[32]; + snprintf(overlay, sizeof(overlay), "%s %+.3f", label, v); + ImGui::ProgressBar(t, ImVec2(-1, 0), overlay); + }; - if (SDL_SetGamepadSensorEnabled(pad, SDL_SENSOR_GYRO, true)) { - float gyro[3]; - if (SDL_GetGamepadSensorData(pad, SDL_SENSOR_GYRO, gyro, 3)) { - bar("X", gyro[0]); - bar("Y", gyro[1]); - bar("Z", gyro[2]); + if (PADSetSensorEnabled(PAD_1, PAD_SENSOR_GYRO, TRUE) == TRUE) { + f32 gyro[3]; + if (PADGetSensorData(PAD_1, PAD_SENSOR_GYRO, gyro, 3) == TRUE) { + bar("X", gyro[0]); + bar("Y", gyro[1]); + bar("Z", gyro[2]); + } + } else { + bar("X", 0.f); + bar("Y", 0.f); + bar("Z", 0.f); } - } else { - bar("X", 0.f); - bar("Y", 0.f); - bar("Z", 0.f); } } diff --git a/src/dusk/imgui/ImGuiEngine.cpp b/src/dusk/imgui/ImGuiEngine.cpp index 0c0b5e934d..4b6a7fb531 100644 --- a/src/dusk/imgui/ImGuiEngine.cpp +++ b/src/dusk/imgui/ImGuiEngine.cpp @@ -3,12 +3,14 @@ #include #include #include +#include #include #include #include #include #include +#include "aurora/lib/window.hpp" #include "dusk/logging.h" #ifdef IMGUI_ENABLE_FREETYPE @@ -37,6 +39,20 @@ ImTextureID AddTexture(const char* assetName) { } return aurora_imgui_add_texture(image.width, image.height, image.data.get()); } + +ImVec2 GetDisplaySafeAreaPadding() { + SDL_Window* window = aurora::window::get_sdl_window(); + if (window == nullptr) { + return ImVec2(0.0f, 0.0f); + } + + SDL_Rect safeRect{}; + if (!SDL_GetWindowSafeArea(window, &safeRect)) { + return ImVec2(0.0f, 0.0f); + } + + return ImVec2(static_cast(safeRect.x), static_cast(safeRect.y)); +} } // namespace ImFont* ImGuiEngine::fontNormal; @@ -75,6 +91,7 @@ void ImGuiEngine_Initialize(float scale) { ImGuiIO& io = ImGui::GetIO(); io.Fonts->Clear(); io.FontGlobalScale = scale > 0.0f ? 1.0f / scale : 1.0f; + io.ConfigWindowsMoveFromTitleBarOnly = IsMobile; ImGuiEngine::fontNormal = CreateFont(std::floor(18.f * scale), GetAssetPath("Inter-Regular.ttf"), "Inter Regular"); @@ -104,11 +121,12 @@ void ImGuiEngine_Initialize(float scale) { style.PopupRounding = 7.0; style.TabBorderSize = 1.f; style.TabRounding = 3.f; + style.DisplaySafeAreaPadding = GetDisplaySafeAreaPadding(); auto* colors = style.Colors; colors[ImGuiCol_Text] = ImVec4(0.95f, 0.96f, 0.98f, 1.00f); colors[ImGuiCol_TextDisabled] = ImVec4(0.36f, 0.42f, 0.47f, 1.00f); - colors[ImGuiCol_WindowBg] = ImVec4(0.11f, 0.15f, 0.17f, 1.00f); + colors[ImGuiCol_WindowBg] = ImVec4(0.11f, 0.15f, 0.17f, 0.98f); colors[ImGuiCol_ChildBg] = ImVec4(0.15f, 0.18f, 0.22f, 1.00f); colors[ImGuiCol_PopupBg] = ImVec4(0.08f, 0.08f, 0.08f, 0.94f); colors[ImGuiCol_Border] = ImVec4(0.08f, 0.10f, 0.12f, 1.00f); @@ -119,7 +137,7 @@ void ImGuiEngine_Initialize(float scale) { colors[ImGuiCol_TitleBg] = ImVec4(0.09f, 0.12f, 0.14f, 0.65f); colors[ImGuiCol_TitleBgActive] = ImVec4(0.08f, 0.10f, 0.12f, 1.00f); colors[ImGuiCol_TitleBgCollapsed] = ImVec4(0.00f, 0.00f, 0.00f, 0.51f); - colors[ImGuiCol_MenuBarBg] = ImVec4(0.15f, 0.18f, 0.22f, 1.00f); + colors[ImGuiCol_MenuBarBg] = ImVec4(0.15f, 0.18f, 0.22f, 0.80f); colors[ImGuiCol_ScrollbarBg] = ImVec4(0.02f, 0.02f, 0.02f, 0.39f); colors[ImGuiCol_ScrollbarGrab] = ImVec4(0.20f, 0.25f, 0.29f, 1.00f); colors[ImGuiCol_ScrollbarGrabHovered] = ImVec4(0.18f, 0.22f, 0.25f, 1.00f); diff --git a/src/dusk/imgui/ImGuiEngine.hpp b/src/dusk/imgui/ImGuiEngine.hpp index 3b3e3e0303..44077c9625 100644 --- a/src/dusk/imgui/ImGuiEngine.hpp +++ b/src/dusk/imgui/ImGuiEngine.hpp @@ -25,4 +25,10 @@ struct Image { uint32_t height; }; Image GetImage(const std::string& path); + +#if (defined(__APPLE__) && TARGET_OS_IOS && !TARGET_OS_MACCATALYST) || defined(__ANDROID__) +inline constexpr bool IsMobile = true; +#else +inline constexpr bool IsMobile = false; +#endif } // namespace dusk diff --git a/src/dusk/imgui/ImGuiEventFlags.hpp b/src/dusk/imgui/ImGuiEventFlags.hpp new file mode 100644 index 0000000000..694336da03 --- /dev/null +++ b/src/dusk/imgui/ImGuiEventFlags.hpp @@ -0,0 +1,842 @@ +#ifndef DUSK_IMGUI_EVENTFLAGS_HPP +#define DUSK_IMGUI_EVENTFLAGS_HPP + +#include +#include + +struct duskImguiEventFlagEntry { + uint8_t byteIndex; + uint8_t bitIndex; + uint16_t flagID; + std::string flagName; + std::string description; + std::string location; +}; + +inline duskImguiEventFlagEntry duskImguiEventFlags[] = { + { 0x00, 0x80, 0x0080, "TEST_001", "Kakariko bridge portal warp hint", "Faron Woods" }, + { 0x00, 0x40, 0x0040, "TEST_002", "Big magma stone portal warp hint", "Death Mountain" }, + { 0x00, 0x20, 0x0020, "TEST_003", "Handed over tomato puree", "LV5 Dungeon" }, + { 0x00, 0x10, 0x0010, "TEST_004", "Handed over secret ingredient", "LV5 Dungeon" }, + { 0x00, 0x08, 0x0008, "F_0001", "Spoke to Renado after Colin returns", "Kakariko Village" }, + { 0x00, 0x04, 0x0004, "F_0002", "Lost wrestling match to elder goron for first time", "Death Mountain (room)" }, + { 0x00, 0x02, 0x0002, "F_0003", "Handed over tomato puree and left room", "LV5 Dungeon" }, + { 0x00, 0x01, 0x0001, "F_0004", "Handed over secret ingredient and left room", "LV5 Dungeon" }, + { 0x01, 0x80, 0x0180, "F_0005", "Gathered 14 Tears of Light in area 4", "Misc." }, + { 0x01, 0x40, 0x0140, "F_0006", "First conversation with Yeto in kitchen", "LV5 Dungeon" }, + { 0x01, 0x20, 0x0120, "F_0007", "Spoke to Yeta while holding cheese", "LV5 Dungeon" }, + { 0x01, 0x10, 0x0110, "F_0008", "First conversation with Fado at the farm on 1st day", "Ordon Village" }, + { 0x01, 0x08, 0x0108, "F_0009", "Approach secret entrance with Colin", "Ordon Woods" }, + { 0x01, 0x04, 0x0104, "F_0010", "First convo with Colin blocking path (forced)", "Ordon Village" }, + { 0x01, 0x02, 0x0102, "F_0011", "Fence jumping complete", "Ranch" }, + { 0x01, 0x01, 0x0101, "F_0012", "Get metal sword!", "Ordon Village" }, + { 0x02, 0x80, 0x0280, "F_0013", "2nd day - First time rampaging goat escapes", "Ordon Village" }, + { 0x02, 0x40, 0x0240, "F_0014", "sword tutorial ends", "Ordon Village" }, + { 0x02, 0x20, 0x0220, "F_0015", "Slingshot tutorial ends", "Ordon Village" }, + { 0x02, 0x10, 0x0210, "F_0016", "On 3rd day, start following Colin who is blocking path", "Ordon Village" }, + { 0x02, 0x08, 0x0208, "F_0017", "Spoke to beth right after sword tutorial", "Ordon Village" }, + { 0x02, 0x04, 0x0204, "F_0018", "Asked by Fado to jump fence", "Ranch" }, + { 0x02, 0x02, 0x0202, "F_0019", "Spoke with Ilia (Colin is there too) at the spring", "Ordon Woods" }, + { 0x02, 0x01, 0x0201, "F_0020", "First convo with Sera while shop is closed", "Ordon Village" }, + { 0x03, 0x80, 0x0380, "D_0001", "Stopped by squirrel in front of house at night", "Ordon Village" }, + { 0x03, 0x40, 0x0340, "F_0021", "2nd day: spoke with Pergie", "Ordon Village" }, + { 0x03, 0x20, 0x0320, "F_0022", "Start fence-jump on 1st day", "Ranch" }, + { 0x03, 0x10, 0x0310, "F_0023", "Called by Jaggle from below hill", "Ordon Village" }, + { 0x03, 0x08, 0x0308, "F_0024", "Spoke with Talo/Malo/Beth (before obtaining slingshot)", "Ordon Village" }, + { 0x03, 0x04, 0x0304, "F_0025", "Pass Uli's pick-up tutorial", "Ordon Village" }, + { 0x03, 0x02, 0x0302, "F_0026", "gave wooden sword to talo on 3rd day", "Ordon Village" }, + { 0x03, 0x01, 0x0301, "F_0027", "Uli tutorial ends (same whether pass or fail)", "Ordon Village" }, + { 0x04, 0x80, 0x0480, "F_0028", "spoke to yeta while holding pumpkin", "LV5 Dungeon" }, + { 0x04, 0x40, 0x0440, "F_0029", "2nd day - refused sword tutorial", "Ordon Village" }, + { 0x04, 0x20, 0x0420, "F_0030", "before sword tutorial - first conversation with Beth", "Ordon Village" }, + { 0x04, 0x10, 0x0410, "F_0031", "2nd day - Spoke to Uli bfore finding basket", "Ordon Village" }, + { 0x04, 0x08, 0x0408, "F_0032", "3rd day - First convo with fado (before forced goat chase)", "Ranch" }, + { 0x04, 0x04, 0x0404, "F_0033", "First day - spoke with Uli", "Ordon Village" }, + { 0x04, 0x02, 0x0402, "M_006", "3rd day - finished chasing goats, speak to Fado in free state", "Ranch" }, + { 0x04, 0x01, 0x0401, "M_007", "first conversation with Shad in basement (about the words of opening)", "Kakariko Village" }, + { 0x05, 0x80, 0x0580, "M_008", "cutscene - attacked by monsters at Ordon spring", "Kawagoe Cutscene" }, + { 0x05, 0x40, 0x0540, "M_009", "[cutscene: 6B] Prison escape - Midna rides on back", "Kawagoe Cutscene" }, + { 0x05, 0x20, 0x0520, "M_010", "[cutscene: 6A] Midna appears in the prison", "Kawagoe Cutscene" }, + { 0x05, 0x10, 0x0510, "M_011", "Midna removes wolf's chains in prison", "Inside Hyrule Castle" }, + { 0x05, 0x08, 0x0508, "M_012", "[cutscene: 7] Meet Princess Zelda at castle", "Kawagoe Cutscene" }, + { 0x05, 0x04, 0x0504, "M_013", "First heard about Twilight gate from Midna", "Misc." }, + { 0x05, 0x02, 0x0502, "M_014", "[cutscene: 8] First warped from castle by Midna", "Kawagoe Cutscene" }, + { 0x05, 0x01, 0x0501, "M_015", "Can use Midna's B charge attack", "Faron Woods" }, + { 0x06, 0x80, 0x0680, "M_016", "[cutscene: 9] Ordon village spirit appears", "Kawagoe Cutscene" }, + { 0x06, 0x40, 0x0640, "M_017", "[cutscene: 10] Dark Hyrule Forest - Midna again", "Kawagoe Cutscene" }, + { 0x06, 0x20, 0x0620, "M_018", "Brought Kakariko bridge back to original location", "main event" }, + { 0x06, 0x10, 0x0610, "M_019", "[cutscene: 11] forest spirit revived - Hero's birth", "Kawagoe Cutscene" }, + { 0x06, 0x08, 0x0608, "M_020", "[cutscene: ] Colin kidnapped : ON once watched", "Kawagoe Cutscene" }, + { 0x06, 0x04, 0x0604, "M_021", "First portal warp", "main event" }, + { 0x06, 0x02, 0x0602, "M_022", "LV1 dungeon clear (Midna creates warp hole)", "LV1 Dungeon" }, + { 0x06, 0x01, 0x0601, "M_023", "Epona rescued flag", "main event" }, + { 0x07, 0x80, 0x0780, "M_024", "[cutscene: 16] take back Colin", "Kawagoe Cutscene" }, + { 0x07, 0x40, 0x0740, "M_025", "First wresting match against fat (elder) goron", "Death Mountain (room)" }, + { 0x07, 0x20, 0x0720, "M_026", "Reunion with Bo (Watched cutscene before wrestle match)", "Ordon Village" }, + { 0x07, 0x10, 0x0710, "M_027", "[cutscene: 13] kids in the church (beast eyes)", "Kawagoe Cutscene" }, + { 0x07, 0x08, 0x0708, "M_028", "[cutscene: 14] restore mountain spirit - Reuinion with Colin et al.", "Kawagoe Cutscene" }, + { 0x07, 0x04, 0x0704, "M_029", "Win wrestle match against Gor Coron", "Death Mountain (room)" }, + { 0x07, 0x02, 0x0702, "M_030", "First conversation with Gor Coron", "Death Mountain (room)" }, + { 0x07, 0x01, 0x0701, "M_031", "LV2 dungeon clear", "LV2 Dungeon" }, + { 0x08, 0x80, 0x0880, "M_032", "Melted Zora river ice with magma rock", "main event" }, + { 0x08, 0x40, 0x0840, "M_033", "Start carriage guarding game", "main event" }, + { 0x08, 0x20, 0x0820, "M_034", "[cutscene: 19] Reunion with Ilia LV3", "Kawagoe Cutscene" }, + { 0x08, 0x10, 0x0810, "M_035", "[cutscene: 35] after carriage guarding event", "Kawagoe Cutscene" }, + { 0x08, 0x08, 0x0808, "M_036", "Begin carriage guarding (after joust revenge)", "main event" }, + { 0x08, 0x04, 0x0804, "M_037", "Got Zora armor from Zora queen", "Kakariko Village" }, + { 0x08, 0x02, 0x0802, "M_038", "[bow and arrow game] listen to hawkeye hint", "Kakariko Village" }, + { 0x08, 0x01, 0x0801, "M_039", "[Bow and arrow game] First time talking to Talo", "Kakariko Village" }, + { 0x09, 0x80, 0x0980, "M_040", "[bow and arrow game] Spoke to Talo after completing", "Kakariko Village" }, + { 0x09, 0x40, 0x0940, "M_041", "[Bow and Arrow game] First attempt", "Kakariko Village" }, + { 0x09, 0x20, 0x0920, "M_042", "[Bow and Arrow game] clear", "Kakariko Village" }, + { 0x09, 0x10, 0x0910, "M_043", "[Bow and Arrow game] Complete using Hawkeye", "Kakariko Village" }, + { 0x09, 0x08, 0x0908, "M_044", "[Barnes Bomb Shop] Bought premium pack", "Kakariko Village" }, + { 0x09, 0x04, 0x0904, "M_045", "LV3 Dungeon clear", "LV3 Dungeon" }, + { 0x09, 0x02, 0x0902, "M_046", "[Iza river descent] Get advice about boulder blocking river", "Zora's River" }, + { 0x09, 0x01, 0x0901, "M_047", "Iza Twilight - Talked after defeating shadow bugs", "Zora's River" }, + { 0x0A, 0x80, 0x0A80, "M_048", "Ran away while clearing rubble at hut", "Zora's River" }, + { 0x0A, 0x40, 0x0A40, "M_049", "Threw first rolling goron at death mountain", "Death Mountain" }, + { 0x0A, 0x20, 0x0A20, "M_050", "Joust bridge disappears", "main event" }, + { 0x0A, 0x10, 0x0A10, "M_051", "Shadow Kargorok (?) (Large) event complete (Horse grass appears in various places)", "main event" }, + { 0x0A, 0x08, 0x0A08, "M_052", "Horseback battle clear", "main event" }, + { 0x0A, 0x04, 0x0A04, "M_053", "Horseback battle cutscene", "main event" }, + { 0x0A, 0x02, 0x0A02, "M_054", "Joust / one-on-one battle cutscene", "main event" }, + { 0x0A, 0x01, 0x0A01, "M_055", "Did damage at least once during joust/one-on-one battle", "main event" }, + { 0x0B, 0x80, 0x0B80, "M_056", "Ignored Iza's concerns", "Ring field" }, + { 0x0B, 0x40, 0x0B40, "M_057", "View boar cutscene after defeating King Bulblin", "Desert" }, + { 0x0B, 0x20, 0x0B20, "M_058", "First time meeting Yeta (forced converation)", "LV5 Dungeon" }, + { 0x0B, 0x10, 0x0B10, "M_059", "received map from Yeta", "LV5 Dungeon" }, + { 0x0B, 0x08, 0x0B08, "M_060", "[Iza river descent] ", "Zora's River" }, + { 0x0B, 0x04, 0x0B04, "M_061", "[Iza river descent] Got explanation for clearing rubble 1 time", "Zora's River" }, + { 0x0B, 0x02, 0x0B02, "M_062", "[Iza] Says thanks after night stalker battle", "Zora's River" }, + { 0x0B, 0x01, 0x0B01, "M_063", "[Iza river descent] Finish job (First time descending river)", "Zora's River" }, + { 0x0C, 0x80, 0x0C80, "M_064", "Water returns to Hylia Lake (spoke with Queen Rutela)", "Zora's Domain" }, + { 0x0C, 0x40, 0x0C40, "M_065", "Spoke with Faron spirit after clearing LV1 dungeon", "Faron Woods" }, + //{ 0x0C, 0x20, 0x0C20, "M_066", "N/A", "N/A" }, + { 0x0C, 0x10, 0x0C10, "M_067", "Midna riding / not riding (ON == riding)", "main event" }, + { 0x0C, 0x08, 0x0C08, "M_068", "when OFF, wolf carries sword and shield on back", "main event" }, + { 0x0C, 0x04, 0x0C04, "M_069", "First conversation with child goron shop clerk", "Kakariko Village" }, + { 0x0C, 0x02, 0x0C02, "M_070", "[cutscene: 18] Lanayru spirit restored", "Kawagoe Cutscene" }, + { 0x0C, 0x01, 0x0C01, "M_071", "[cutscene: 20] Zant appears (during Midna's desperate hour)", "Kawagoe Cutscene" }, + { 0x0D, 0x80, 0x0D80, "M_072", "Get wooden shield", "Ordon Village" }, + { 0x0D, 0x40, 0x0D40, "M_073", "Spoke with Renado after guarding carriage [0030]", "Kakariko Village" }, + { 0x0D, 0x20, 0x0D20, "M_074", "Spoke with Renado after guarding carriage [0031]", "Kakariko Village" }, + { 0x0D, 0x10, 0x0D10, "M_075", "Forced conversation with Yeta after getting bedroom key", "LV5 Dungeon" }, + { 0x0D, 0x08, 0x0D08, "M_076", "First conversation with Castle Town Malo Mart shop clerk", "Misc." }, + { 0x0D, 0x04, 0x0D04, "M_077", "Get shadow crystal (can now transform)", "main event" }, + { 0x0D, 0x02, 0x0D02, "M_078", "Spoke with frog A", "Ordon Village" }, + { 0x0D, 0x01, 0x0D01, "M_079", "Ordon village night: Heard Z Jump dialogie in Pergie's house", "Ordon Village" }, + { 0x0E, 0x80, 0x0E80, "M_080", "Spoke with brown cucco", "Ordon Village" }, + { 0x0E, 0x40, 0x0E40, "M_081", "First conversation with Goron that shoots you up (shared with everyone)", "Death Mountain" }, + { 0x0E, 0x20, 0x0E20, "M_082", "Spoke with spring Goron A", "Death Mountain" }, + { 0x0E, 0x10, 0x0E10, "M_083", "Heard Fyer's talk after water restored", "Lake Hylia" }, + { 0x0E, 0x08, 0x0E08, "M_084", "Complete sequence of shopping at Malo Mart first time", "Kakariko Village" }, + { 0x0E, 0x04, 0x0E04, "M_085", "Midna dialogue right before Boss Bug's Tear of Light appears", "Twilight Ring field" }, + { 0x0E, 0x02, 0x0E02, "M_086", "Show Boss Bug's Tear of Light on the map", "Twilight Ring field" }, + { 0x0E, 0x01, 0x0E01, "M_087", "Ilia memory event start", "Ring field" }, + { 0x0F, 0x80, 0x0F80, "M_088", "Get Renado's Letter", "Kakariko Village" }, + { 0x0F, 0x40, 0x0F40, "M_089", "First time entering doctor's office (forced conversation)", "Castle Town" }, + { 0x0F, 0x20, 0x0F20, "M_090", "Spoke to town doctor before showing receipt", "Castle Town" }, + { 0x0F, 0x10, 0x0F10, "M_091", "Buy out fundraiser amount (Malo becomes nice)", "Kakariko Village" }, + { 0x0F, 0x08, 0x0F08, "M_092", "Warped Eldin Bridge", "Ring field" }, + { 0x0F, 0x04, 0x0F04, "M_093", "First conversation with Fyer after desert's debut", "Lake Hylia" }, + { 0x0F, 0x02, 0x0F02, "M_094", "First time visiting Rizu's hut after completing river job (forced conversation)", "Zora's River" }, + { 0x0F, 0x01, 0x0F01, "M_095", "First time meeting Coro (obtain lantern)", "Faron Woods" }, + { 0x10, 0x80, 0x1080, "M_096", "3rd day: spoke with Pergie", "Ordon Village" }, + { 0x10, 0x40, 0x1040, "F_0034", "first conversation wtih Rusl", "Ordon Village" }, + { 0x10, 0x20, 0x1020, "F_0035", "F0003: Spoke to Colin while he is stopping hors (doesn't have fishing rod)", "Ordon Village" }, + { 0x10, 0x10, 0x1010, "M_001", "Opening cutscene", "Kawagoe Cutscene" }, + { 0x10, 0x08, 0x1008, "M_003", "F0003: Spoke to Colin while he is stopping hors (has fishing rod)", "Ordon Village" }, + { 0x10, 0x04, 0x1004, "F_0036", "Spoke to Jaggle using L-focus before climbing vines?", "Ordon Village" }, + { 0x10, 0x02, 0x1002, "F_0037", "Jaggle - Spoke on the hill?", "Ordon Village" }, + { 0x10, 0x01, 0x1001, "F_0038", "Opening (2nd day) cat returns home", "Ordon Village" }, + { 0x11, 0x80, 0x1180, "F_0039", "Warned by Hanch after climbing vines on 2nd day", "Ordon Village" }, + { 0x11, 0x40, 0x1140, "F_0040", "2nd day: Spoke to Jaggle after blowing on whistle", "Ordon Village" }, + { 0x11, 0x20, 0x1120, "F_0041", "Opening 2nd day - After this is turned ON Hanch is attacked by bees", "Ordon Village" }, + { 0x11, 0x10, 0x1110, "F_0042", "Spoke with Yeta right after arriving at bedroom", "LV5 Dungeon" }, + { 0x11, 0x08, 0x1108, "F_0043", "First visit after fundrasing funds drop to 200", "Kakariko Village" }, + { 0x11, 0x04, 0x1104, "F_0044", "Accepted sword tutorial first time", "Ordon Village" }, + { 0x11, 0x02, 0x1102, "F_0045", "Opening 3rd day - spoke with Uli", "Ordon Village" }, + { 0x11, 0x01, 0x1101, "F_0046", "Spoke with Sera after saving(lt;) failing(gt;) cat", "Ordon Village" }, + { 0x12, 0x80, 0x1280, "F_0047", "First visit after Ordon Village shop opens", "Ordon Village" }, + { 0x12, 0x40, 0x1240, "F_0048", "Uli's pick-up tutorial ", "Ordon Village" }, + { 0x12, 0x20, 0x1220, "F_0049", "Uli's pick up tutorial ", "Ordon Village" }, + { 0x12, 0x10, 0x1210, "F_0050", "Saw cutscene for getting iron boots", "Ordon Village" }, + { 0x12, 0x08, 0x1208, "F_0051", "Spoke to Sera in the shop after saving cat", "Ordon Village" }, + { 0x12, 0x04, 0x1204, "F_0052", "Had 2nd conversaton with Sera before saving cat", "Ordon Village" }, + { 0x12, 0x02, 0x1202, "F_0053", "Saw night stalker appearance cutscene", "Faron Woods" }, + { 0x12, 0x01, 0x1201, "F_0054", "Lost wrestling match with elder goron while wearing iron boots", "Death Mountain" }, + { 0x13, 0x80, 0x1380, "F_0055", "Received Vessel of Light from Faron spirit", "Faron Woods" }, + { 0x13, 0x40, 0x1340, "F_0056", "Lost to elder goron 2+ times", "Death Mountain" }, + { 0x13, 0x20, 0x1320, "F_0057", "[cutscene: 17] Part with the children", "Kawagoe Cutscene" }, + { 0x13, 0x10, 0x1310, "F_0058", "Listened to voices on other side of door in Telma's shop", "Castle Town" }, + { 0x13, 0x08, 0x1308, "F_0059", "Conversation after getting spirit and tears of light (darkness cleared) <- probably unused ...", "+ Scary flag" }, + { 0x13, 0x04, 0x1304, "F_0060", "First conversation with Hozu in the World of Light", "Zora's River" }, + { 0x13, 0x02, 0x1302, "F_0061", "Heard spring goron and shopkeeper rumers after winning wrestling match against elder goron", "Death Mountain" }, + { 0x13, 0x01, 0x1301, "F_0062", "Abandoned taking Fyer's cannon after paying", "Lake Hylia" }, + { 0x14, 0x80, 0x1480, "F_0063", "Used Fyer's cannon for first time", "Lake Hylia" }, + { 0x14, 0x40, 0x1440, "F_0064", "first convo with yeta after obtaining tomato puree", "LV5 Dungeon" }, + { 0x14, 0x20, 0x1420, "F_0065", "Yeta adds last symbol onto map", "LV5 Dungeon" }, + { 0x14, 0x10, 0x1410, "F_0066", "First saw Goron cutscene on mountain path", "Death Mountain" }, + { 0x14, 0x08, 0x1408, "F_0067", "Recieved milk jar (1/2) from Sera", "Ordon Village" }, + //{ 0x14, 0x04, 0x1404, "F_0068", "N/A", "N/A" }, + { 0x14, 0x02, 0x1402, "F_0069", "F0048: Thanked by Colen for clearing path", "Ordon Village" }, + { 0x14, 0x01, 0x1401, "F_0070", "Colin went deep into the woods", "Ordon Woods" }, + { 0x15, 0x80, 0x1580, "M_002", "[cutscene: 2] Met with Ilia (brings horse to spring)", "Kawagoe Cutscene" }, + { 0x15, 0x40, 0x1540, "F_0071", "Cannot warp to Lanayru", "Twilight Ring field" }, + { 0x15, 0x20, 0x1520, "F_0072", "Knocked down large beehive with hawk", "Ordon Village" }, + { 0x15, 0x10, 0x1510, "F_0073", "Attacked after charging at large beehive", "Ordon Village" }, + { 0x15, 0x08, 0x1508, "F_0074", "Hanch attacked by bees", "Ordon Village" }, + { 0x15, 0x04, 0x1504, "F_0075", "Angered Jaggle by destroying pumpkin", "Ordon Village" }, + { 0x15, 0x02, 0x1502, "F_0076", "Spoke to Hanch in lake", "Ordon Village" }, + { 0x15, 0x01, 0x1501, "F_0077", "First converstaion with Agetha inside", "Castle Town" }, + { 0x16, 0x80, 0x1680, "F_0078", "Hanch returned to land after jumping into lake", "Ordon Village" }, + { 0x16, 0x40, 0x1640, "F_0079", "2nd Day - successful knocked down rampaging mountain goat", "Ordon Village" }, + { 0x16, 0x20, 0x1620, "F_0080", "Completed all of mountain goat rampage event", "Ordon Village" }, + { 0x16, 0x10, 0x1610, "F_0081", "20 mountain goats rampaged", "Ordon Village" }, + { 0x16, 0x08, 0x1608, "F_0082", "Completed coversation with Bo after 20th mountain goat's rampage", "Ordon Village" }, + { 0x16, 0x04, 0x1604, "F_0083", "Deliver letter from Agetha", "Letter" }, + { 0x16, 0x02, 0x1602, "F_0084", "Opening days 2&3: knocked down a beehive with slingshot", "Ordon Village" }, + { 0x16, 0x01, 0x1601, "F_0085", "Rusl appears at woods entrance", "Ordon Village" }, + { 0x17, 0x80, 0x1780, "F_0086", "Spoke with Hanch after knocking down beehive with hawk", "Ordon Village" }, + { 0x17, 0x40, 0x1740, "F_0087", "Left search area after first conversation with Pergie", "Ordon Village" }, + { 0x17, 0x20, 0x1720, "F_0088", "Spoke to Beth after quitting sword tutorial", "Ordon Village" }, + { 0x17, 0x10, 0x1710, "F_0089", "Talked to village chief for first time", "Ordon Village" }, + { 0x17, 0x08, 0x1708, "F_0090", "F:1126 - South - Spoke with Agetha's stalker (before talking with Agetha inside)", "Castle Town" }, + { 0x17, 0x04, 0x1704, "F_0091", "F:1126 - South - Spoke with Agetha's stalker (after talking with Agetha inside)", "Ordon Village" }, + { 0x17, 0x02, 0x1702, "F_0092", "F:1127 - South - Spoke with the Hyrule soldier guide", "Castle Town" }, + { 0x17, 0x01, 0x1701, "F_0093", "F:1128 - South - Spoke with female clerk at vegetable stand", "Castle Town" }, + { 0x18, 0x80, 0x1880, "F_0094", "Talo went after the monkey", "Ordon Village" }, + { 0x18, 0x40, 0x1840, "F_0095", "Spoke to Fado before mountain goat rampage", "Ranch" }, + { 0x18, 0x20, 0x1820, "F_0096", "Have spoken to Bo with 1 health", "Ordon Village" }, + { 0x18, 0x10, 0x1810, "F_0097", "First conversation with dog eavesdrop hint", "Ordon Village" }, + { 0x18, 0x08, 0x1808, "F_0202", "Rusl / Wolf fails to get sword", "Ordon Village" }, + { 0x18, 0x04, 0x1804, "F_0203", "First tried to steal from unnmaned shop (Havent checked donation box)", "Shop" }, + { 0x18, 0x02, 0x1802, "F_0204", "Talked to Midna from across the bars in the sewer", "Inside Hyrule Castle" }, + { 0x18, 0x01, 0x1801, "F_0205", "Heard Rusl and Uli talking in Ordon village at night", "Ordon Village" }, + { 0x19, 0x80, 0x1980, "F_0206", "Barrier of darkness tag: tried to enter without getting sword and shield", "Ordon Woods" }, + { 0x19, 0x40, 0x1940, "F_0207", "Viewed Hanch, the Hawker cutscene", "Ordon Village" }, + { 0x19, 0x20, 0x1920, "F_0208", "Hanch leapt after being startled by wolf", "Ordon Village" }, + { 0x19, 0x10, 0x1910, "F_0209", "Escape after failing to eavesdrop to Bo and Jaggle", "Ordon Village" }, + { 0x19, 0x08, 0x1908, "F_0210", "F0004: Try to get on horse when Colin is stopping horse", "Ordon Village" }, + { 0x19, 0x04, 0x1904, "F_0211", "Successfully eavesdrop on Bo and Jaggle", "Ordon Village" }, + { 0x19, 0x02, 0x1902, "F_0212", "Conversation wtih Colin in front of Ordon springs gate", "Ordon Woods" }, + { 0x19, 0x01, 0x1901, "F_0213", "Called by spirit after NS fight at Kakariko entrance", "Kakariko Village" }, + { 0x1A, 0x80, 0x1A80, "F_0214", " First heard Midna's hint about Z", "Inside Hyrule Castle" }, + { 0x1A, 0x40, 0x1A40, "F_0215", "Spoke with cat on roof", "Ordon Village" }, + { 0x1A, 0x20, 0x1A20, "F_0216", "Spoke with Ordon village cucco B (white)", "Ordon Village" }, + { 0x1A, 0x10, 0x1A10, "F_0217", "First conversation after speaking with Coro again after clearing up twilight", "Faron Woods" }, + { 0x1A, 0x08, 0x1A08, "F_0218", "Bought jar of oil from Coro", "Faron Woods" }, + { 0x1A, 0x04, 0x1A04, "F_0219", "Heard Agetha say \"but you have some\"", "Castle Town" }, + { 0x1A, 0x02, 0x1A02, "F_0220", "First time - Talked to one of the Hyrule soldiers underneath castle in Twilight", "Inside Hyrule Castle" }, + { 0x1A, 0x01, 0x1A01, "F_0221", "Received vessel of light from spirit", "Kakariko Village" }, + { 0x1B, 0x80, 0x1B80, "F_0222", "Finished Coro ignite event", "Faron Woods" }, + { 0x1B, 0x40, 0x1B40, "F_0223", "Listened to Coro's dialogue before defeating shadow bugs", "Farron Woods inside woodcutter's shop" }, + { 0x1B, 0x20, 0x1B20, "F_0224", "Flag for lantern guide monkey cutscene", "Faron Woods" }, + { 0x1B, 0x10, 0x1B10, "F_0225", "Lanter guide monkey doesn't come out a second time", "Faron Woods" }, + { 0x1B, 0x08, 0x1B08, "F_0226", "Get lantern back from monkey", "Faron Woods" }, + { 0x1B, 0x04, 0x1B04, "F_0227", "Try to leave mist woods after lantern taken", "Faron Woods" }, + { 0x1B, 0x02, 0x1B02, "F_0228", "Listened to goron's complaint at the entrance to twilight mountain path", "Death Mountain" }, + { 0x1B, 0x01, 0x1B01, "F_0229", "Try to leave the mist woods area without lantern (after returning monkey)", "Faron Woods" }, + { 0x1C, 0x80, 0x1C80, "F_0230", "Twilight - listened to goron B's complaints", "Death Mountain" }, + { 0x1C, 0x40, 0x1C40, "F_0231", "Did first wrestle match with Bo", "Ordon Village" }, + { 0x1C, 0x20, 0x1C20, "F_0232", "get iron boots from Bo", "Ordon Village" }, + { 0x1C, 0x10, 0x1C10, "F_0233", "Win practice battle with Bo (before getting boots)", "Ordon Village" }, + { 0x1C, 0x08, 0x1C08, "F_234", "Attacked by Trill", "Faron Woods" }, + { 0x1C, 0x04, 0x1C04, "F_0235", "Giant game clear", "Sacred Grove" }, + { 0x1C, 0x02, 0x1C02, "F_0236", "Make Midna angry after doing wrong destination for Kakariko bridge warp", "Misc." }, + { 0x1C, 0x01, 0x1C01, "F_0237", "Entered Malo Mart (chatted with Malo)", "Kakariko Village" }, + { 0x1D, 0x80, 0x1D80, "F_0238", "Destroyed all puppets (monkey girl event before sacred grove)", "Faron Woods" }, + { 0x1D, 0x40, 0x1D40, "T_0239", "Spoke with Fyer (start dark carge)", "Lake Hylia" }, + { 0x1D, 0x20, 0x1D20, "F_0240", "Speak with Barnes while heas making bombs", "Kakariko Village" }, + { 0x1D, 0x10, 0x1D10, "F_0241", "First conversation with Barnes after bomb shop open", "Kakariko Village" }, + { 0x1D, 0x08, 0x1D08, "F_0242", "Spoke with the dazed chief goron after Fyrus battle", "LV2 Dungeon" }, + { 0x1D, 0x04, 0x1D04, "F_0243", "Speak with Luda wiping Colin's sweat", "Kakariko Village" }, + { 0x1D, 0x02, 0x1D02, "F_0244", "Speak to Talo while COlin is wiping sweat", "Kakariko Village" }, + { 0x1D, 0x01, 0x1D01, "MAP_VISIBLE", "Area map show/hide", "2D Map" }, + { 0x1E, 0x80, 0x1E80, "F_0246", "Malo Mart fundraiser (and carying spring water) start", "Kakariko Village" }, + { 0x1E, 0x40, 0x1E40, "F_0247", "First conversation with Malo (shopping complete) after helping Malo Mart fundraiser", "Kakariko Village" }, + { 0x1E, 0x20, 0x1E20, "F_0248", "Speak with Coro after clearing Forest Temple", "Faron Woods" }, + { 0x1E, 0x10, 0x1E10, "F_0249", "Try to leave after paying for Flight by Fowl (first time only)", "Lake Hylia" }, + { 0x1E, 0x08, 0x1E08, "F_0250", "[cutscene: 21] reunion with Zelda / Midna revived (Ganon wall appears)", "Kawagoe Cutscene" }, + { 0x1E, 0x04, 0x1E04, "F_0251", "Speak again with Sera before finding kids (forced conversation)", "Ordon Village" }, + { 0x1E, 0x02, 0x1E02, "F_0252", "Spoke to Sera while kids are missing (dealing with shop)", "Ordon Village" }, + { 0x1E, 0x01, 0x1E01, "F_0253", "Stuck to magnet lift at least once", "For E3 2006" }, + { 0x1F, 0x80, 0x1F80, "F_0254", "Hit boss's weak spot at least once", "For E3 2006" }, + { 0x1F, 0x40, 0x1F40, "F_0255", "Boss exhausted (grabbing chains) only on during state (normally off)", "For E3 2006" }, + { 0x1F, 0x20, 0x1F20, "F_0256", "Knocked down boss at leased once", "For E3 2006" }, + { 0x1F, 0x10, 0x1F10, "F_0257", "Only ON when boss is in hollow state (normally off, changes in real time)", "For E3 2006" }, + { 0x1F, 0x08, 0x1F08, "F_0258", "Heard hint right above heavy switch", "For E3 2006" }, + { 0x1F, 0x04, 0x1F04, "F_0259", "Heard first forced dialogue from Midna", "For E3 2006" }, + { 0x1F, 0x02, 0x1F02, "F_0260", "First time speaking to Resistance Rusl", "Castle Town" }, + { 0x1F, 0x01, 0x1F01, "F_0261", "First conversation with Resistance Auru (made fun of)", "Castle Town" }, + { 0x20, 0x80, 0x2080, "F_0262", "First conversation with Resistance Shad (made fun of)", "Castle Town" }, + { 0x20, 0x40, 0x2040, "F_0263", "First conversation with Resistance Ashei (3) (made fun of)", "Castle Town" }, + { 0x20, 0x20, 0x2020, "F_0264", "get master sword", "Kawagoe Cutscene" }, + { 0x20, 0x10, 0x2010, "F_0265", "LV4 dungeon clear", "LV4 Dungeon," }, + { 0x20, 0x08, 0x2008, "F_0266", "LV5 dungeon clear", "LV5 Dungeon" }, + { 0x20, 0x04, 0x2004, "F_0267", "LV6 dungeon clear", "LV6 Dungeon" }, + { 0x20, 0x02, 0x2002, "F_0268", "LV7 dungeon clear", "LV7 dungeon" }, + { 0x20, 0x01, 0x2001, "F_0269", "First conversation with Telma after getting master sword", "Castle Town" }, + { 0x21, 0x80, 0x2180, "F_0270", "Gave Renado's letter to Telma", "Castle Town" }, + { 0x21, 0x40, 0x2140, "F_0271", "Talked with Telma again after the other conversation after getting master sword", "Castle Town" }, + { 0x21, 0x20, 0x2120, "F_0272", "Saw Auru's location on map", "Castle Town" }, + { 0x21, 0x10, 0x2110, "F_0273", "Saw Ashei's location on map", "Castle Town" }, + { 0x21, 0x08, 0x2108, "F_0274", "Saw Rusl's location on map", "Castle Town" }, + { 0x21, 0x04, 0x2104, "F_0275", "Saw Shad's location on map", "Castle Town" }, + { 0x21, 0x02, 0x2102, "F_0276", "Heard conversation with Louise about stolen wood carving", "Castle Town" }, + { 0x21, 0x01, 0x2101, "F_0277", "Hear conversation between Telma and Ilia in Telma's shop (Twilight)", "Castle Town" }, + { 0x22, 0x80, 0x2280, "F_0278", "Received pendant from Impaz", "Hidden Village" }, + { 0x22, 0x40, 0x2240, "F_0279", "Saw cutscene about scent of kids from wooden sword", "Ring field" }, + { 0x22, 0x20, 0x2220, "F_0280", "Saw cutscene about Ilia's scent from pouch", "Ring field" }, + { 0x22, 0x10, 0x2210, "F_0281", "Malo Mart opens in Castle Town", "Shop" }, + { 0x22, 0x08, 0x2208, "F_282", "First conversation with Yeto at peak after LV5 dungeon clear", "Snowpeak mountain" }, + { 0x22, 0x04, 0x2204, "F_283", "Get wood carving", "Ring field" }, + { 0x22, 0x02, 0x2202, "F_284", "Already have a score recorded for Plumm's game", "Lake Hylia" }, + { 0x22, 0x01, 0x2201, "F_285", "First conversation with Plumm as wolf", "Lake Hylia" }, + { 0x23, 0x80, 0x2380, "F_286", "Cleared Plumm's attraction (get heart piece)", "Lake Hylia" }, + { 0x23, 0x40, 0x2340, "F_287", "Handed wood carving to Ilia", "Kakariko Village" }, + { 0x23, 0x20, 0x2320, "F_288", "[cutscene: ] Ilia gets her memories back", "Kawagoe Cutscene" }, + { 0x23, 0x10, 0x2310, "F_289", "Heard conversation about entering double clawshot game LV1", "Castle Town" }, + { 0x23, 0x08, 0x2308, "F_290", "Double clawshot game LV1 cleared", "Castle Town" }, + { 0x23, 0x04, 0x2304, "M_097", "Can use magic", "main event" }, + { 0x23, 0x02, 0x2302, "F_0292", "Star Game 2 first experience", "Castle Town" }, + { 0x23, 0x01, 0x2301, "F_0293", "Star Game 2 cleared", "Castle Town" }, + { 0x24, 0x80, 0x2480, "F_0294", "Shaman - Prayers reached the heavens (heart piece obtained complete)", "Castle Town" }, + { 0x24, 0x40, 0x2440, "F_0295", "Watched meeting Louise event when sneaking into Telma's shop (wolf)", "Castle Town" }, + { 0x24, 0x20, 0x2420, "F_0296", "Spoke with Louise after kicked being kicked out of Telma's shop", "Castle Town" }, + { 0x24, 0x10, 0x2410, "F_0297", "First spoke with Chudley store clerk", "Castle Town" }, + { 0x24, 0x08, 0x2408, "F_0298", "Spoke with Auru after completing LV4 dungeon", "Castle Town" }, + { 0x24, 0x04, 0x2404, "F_0299", "Spoke with Shad at Telma's shop after getting master sword", "Castle Town" }, + { 0x24, 0x02, 0x2402, "F_0300", "Heard about Sky People from Shad at Telma's shop", "Castle Town" }, + { 0x24, 0x01, 0x2401, "F_0301", "Spoke with Shad after LV7 dungeon clear", "Castle Town" }, + { 0x25, 0x80, 0x2580, "F_0302", "Saw cutscene of Shad casting spells underneat Kakariko Village", "Kakariko Village" }, + { 0x25, 0x40, 0x2540, "F_0303", "Saw Shad's spell 2", "Kakariko Village" }, + { 0x25, 0x20, 0x2520, "F_0304", "Spoke with Auru at Hylia Lake", "Lake Hylia" }, + { 0x25, 0x10, 0x2510, "F_0305", "Heard about Fyer from Auru (desert cannon ON)", "Lake Hylia" }, + { 0x25, 0x08, 0x2508, "F_0306", "Used Fyer's cannon to go to desert", "Desert" }, + { 0x25, 0x04, 0x2504, "F_0307", "Spoke with Yeto at top of mountain as wolf", "Snowpeak mountain" }, + { 0x25, 0x02, 0x2502, "F_0308", "Watched first meeting event with Yeto at top of mountain (human)", "Snowpeak mountain" }, + { 0x25, 0x01, 0x2501, "F_0309", "First conversation with Agetha inside (gives golden bug capture quest)", "Castle Town" }, + { 0x26, 0x80, 0x2680, "F_0310", "Hand Auru's note to Fyer", "Lake Hylia" }, + { 0x26, 0x40, 0x2640, "F_0311", "LV8 Dungeon use 1", "LV8 Dungeon" }, + { 0x26, 0x20, 0x2620, "F_0312", "LV8 Dungeon use 2", "LV8 Dungeon" }, + { 0x26, 0x10, 0x2610, "F_0313", "LV8 Dungeon use 3", "LV8 Dungeon" }, + { 0x26, 0x08, 0x2608, "F_0314", "LV8 Dungeon use 4", "LV8 Dungeon" }, + { 0x26, 0x04, 0x2604, "F_0315", "LV8 Dungeon use 5", "LV8 Dungeon" }, + { 0x26, 0x02, 0x2602, "F_0316", "LV8 Dungeon use 6", "LV8 Dungeon" }, + { 0x26, 0x01, 0x2601, "F_0317", "LV8 Dungeon use 7", "LV8 Dungeon" }, + { 0x27, 0x80, 0x2780, "F_0318", "LV8 Dungeon use 8", "LV8 Dungeon" }, + { 0x27, 0x40, 0x2740, "F_0319", "LV8 Dungeon use 9", "LV8 Dungeon" }, + { 0x27, 0x20, 0x2720, "F_0320", "LV8 Dungeon use 10", "LV8 Dungeon" }, + { 0x27, 0x10, 0x2710, "F_0321", "Showed reciept to town doctor", "Castle Town" }, + { 0x27, 0x08, 0x2708, "F_0322", "Flow 0010 Spoke with Zora in Zora armor", "Zora's Domain" }, + { 0x27, 0x04, 0x2704, "F_0323", "Flow 0020 Spoke with Zora", "Zora's Domain" }, + { 0x27, 0x02, 0x2702, "F_0324", "Flow 0023 Speak with Zora before blowing up magma rock", "Zora's Domain" }, + { 0x27, 0x01, 0x2701, "F_0325", "Flow 0023 Speak with Zora after blowing up magma rock", "Zora's Domain" }, + { 0x28, 0x80, 0x2880, "F_0326", "LV8 mini-boss defeated flag", "LV8 Dungeon" }, + { 0x28, 0x40, 0x2840, "F_0327", "First time warped by Ooccoo Sr.", "Misc." }, + { 0x28, 0x20, 0x2820, "F_0328", "Talk again with Jaggle after finding kids", "Ordon Village" }, + { 0x28, 0x10, 0x2810, "F_0329", "Jaggle brings up shield", "Ordon Village" }, + { 0x28, 0x08, 0x2808, "F_0330", "Meet again with Uli for the first time (first forced conversation)", "Ordon Village" }, + { 0x28, 0x04, 0x2804, "F_0331", "Meet again and talk with Uli after finding kids (1st time)", "Ordon Village" }, + { 0x28, 0x02, 0x2802, "F_0332", "Meet again and talk with Uli after finding kids (2nd time)", "Ordon Village" }, + { 0x28, 0x01, 0x2801, "F_0333", "Uli brings up the sword", "Ordon Village" }, + { 0x29, 0x80, 0x2980, "F_0334", "Spoke with Ashei at Telma's shop after getting master sword", "Castle Town" }, + { 0x29, 0x40, 0x2940, "F_0335", "Obtained scribble from Ashei at mountain pass", "Snowpeak mountain" }, + { 0x29, 0x20, 0x2920, "F_0336", "Blew up molten rock bomb ", "Zora's Domain" }, + { 0x29, 0x10, 0x2910, "F_0337", "Listened to old ladies A/B conversation (wolf / Ralis hint FLOW104)", "Castle Town" }, + { 0x29, 0x08, 0x2908, "F_0338", "Obtained 1 secret techinques - Shield attack", "Secret techniques" }, + { 0x29, 0x04, 0x2904, "F_0339", "Obtained 2 secret techinques", "Secret techniques" }, + { 0x29, 0x02, 0x2902, "F_0340", "Obtained 3 secret techinques", "Secret techniques" }, + { 0x29, 0x01, 0x2901, "F_0341", "Obtained 4 secret techinques", "Secret techniques" }, + { 0x2A, 0x80, 0x2A80, "F_0342", "Obtained 5 secret techinques", "Secret techniques" }, + { 0x2A, 0x40, 0x2A40, "F_0343", "Obtained 6 secret techinques", "Secret techniques" }, + { 0x2A, 0x20, 0x2A20, "F_0344", "Obtained 7 secret techinques", "Secret techniques" }, + { 0x2A, 0x10, 0x2A10, "F_0345", "Opening 2nd day - lit Coro's pot", "Faron Woods" }, + { 0x2A, 0x08, 0x2A08, "F_0346", "Turned down Auru's request to go to the desert", "Lake Hylia" }, + { 0x2A, 0x04, 0x2A04, "F_0347", "Spoke with Ashei after LV5 dungeon clear ", "Castle Town" }, + { 0x2A, 0x02, 0x2A02, "F_0348", "Spoke with Zora soldier in front of LV3 dungeon (before bombing entrance)", "Lake Hylia" }, + { 0x2A, 0x01, 0x2A01, "F_0349", "Spoke with Zora soldier in front of LV3 dungeon (after bombing entrance)", "Lake Hylia" }, + { 0x2B, 0x80, 0x2B80, "F_0350", "Showed wood carving doll to Renado", "Kakariko Village" }, + { 0x2B, 0x40, 0x2B40, "F_0351", "First meeting with Agether outside (haven't met inside yet) : gives bug even quest", "Ring field" }, + { 0x2B, 0x20, 0x2B20, "F_0352", "Second conversation with Agetha (outside)", "Ring field" }, + { 0x2B, 0x10, 0x2B10, "F_0353", "Conversation with lady complaining about Chudley's shop", "Castle Town" }, + { 0x2B, 0x08, 0x2B08, "F_0354", "[cutscene] Mirror complete", "Kawagoe Cutscene" }, + { 0x2B, 0x04, 0x2B04, "F_0355", "Showed wood carving to village doctor", "Castle Town" }, + { 0x2B, 0x02, 0x2B02, "F_0356", "Spoke with village doctor after Ilia's memories restored", "Castle Town" }, + { 0x2B, 0x01, 0x2B01, "F_0357", "Spoke with Telma's guardian goron after she came back", "Death Mountain" }, + { 0x2C, 0x80, 0x2C80, "F_0358", "Spoke with Telma after Ilia's memories restored", "Castle Town" }, + { 0x2C, 0x40, 0x2C40, "F_0359", "Spoke with Ilia while she's watching over Ralis", "Kakariko Village" }, + { 0x2C, 0x20, 0x2C20, "F_0360", "Conversation with fortune teller as wolf", "Castle Town" }, + { 0x2C, 0x10, 0x2C10, "F_0361", "Spun the spinning pillars", "LV4 Dungeon," }, + { 0x2C, 0x08, 0x2C08, "F_0362", "Properly spoke with Resistance Rusl again (2nd time)", "Castle Town" }, + { 0x2C, 0x04, 0x2C04, "F_0363", "Stole sword from Rusl in Ordon Village at night (wolf)", "Ordon Village" }, + { 0x2C, 0x02, 0x2C02, "F_0364", "Listened to monkey girl's laments (Twilight)", "Faron Woods" }, + { 0x2C, 0x01, 0x2C01, "F_0365", "Spoke with Gor Liggs after LV dungeon clear", "Death Mountain (room)" }, + { 0x2D, 0x80, 0x2D80, "F_0366", "Spoke with Gor Liggs after Ilia's memories restored", "Death Mountain (room)" }, + { 0x2D, 0x40, 0x2D40, "F_0367", "Spoke with goron blocking mountain path after LV2 dungeon clear", "Death Mountain" }, + { 0x2D, 0x20, 0x2D20, "F_0368", "First conversation wtih goron in front of hotel", "Kakariko Village" }, + { 0x2D, 0x10, 0x2D10, "F_0369", "First conversation with goron in front of watch tower", "Kakariko Village" }, + { 0x2D, 0x08, 0x2D08, "F_0370", "First converstaion with Karakiko springs goron (adult)", "Kakariko Village" }, + { 0x2D, 0x04, 0x2D04, "F_0371", "First conversation wtih goron in front of bomb shop (while only selling bombs)", "Kakariko Village" }, + { 0x2D, 0x02, 0x2D02, "F_0372", "Tried to by milk before saving cat", "Ordon Village" }, + { 0x2D, 0x01, 0x2D01, "F_0373", "Coversation with Darbus after Ilia's memories restored", "Death Mountain" }, + { 0x2E, 0x80, 0x2E80, "F_0374", "Conversation with Darbus in wrestling room after LV2 dungeon clear", "Death Mountain" }, + { 0x2E, 0x40, 0x2E40, "F_0375", "First conversation with fundraising goron at Malo Mart", "Kakariko Village" }, + { 0x2E, 0x20, 0x2E20, "F_0376", "Gathered funds for bridge repair! (set by program after raising funds)", "Kakariko Village" }, + { 0x2E, 0x10, 0x2E10, "F_0377", "Goron spring water shop open!", "Castle Town" }, + { 0x2E, 0x08, 0x2E08, "F_0378", "Darbus destroyed hidden village boulder", "Ring field" }, + { 0x2E, 0x04, 0x2E04, "F_0379", "Completed golden bugs", "N/A" }, + { 0x2E, 0x02, 0x2E02, "F_0380", "Complete first meeting with Agetha (Recieved bug collection quest)", "N/A" }, + { 0x2E, 0x01, 0x2E01, "F_0381", "First conversation with parent goron after spring water shop opens", "Castle Town" }, + { 0x2F, 0x80, 0x2F80, "F_0382", "Listened to laments of fallen goron (while bridge broken)", "Castle Town" }, + { 0x2F, 0x40, 0x2F40, "F_0383", "Spoke with child goron after spring water shop opens", "Castle Town" }, + { 0x2F, 0x20, 0x2F20, "F_0384", "Spring water shop - Spoke with child goron while shop closed", "Castle Town" }, + { 0x2F, 0x10, 0x2F10, "F_0385", "First conversation with Barnes after stocking water bombs", "Kakariko Village" }, + { 0x2F, 0x08, 0x2F08, "F_0386", "Conversation with Barnes after he starts selling Bomblings and landmines", "Kakariko Village" }, + { 0x2F, 0x04, 0x2F04, "F_0387", "Learned scent of medicine", "Castle Town" }, + { 0x2F, 0x02, 0x2F02, "F_0388", "Graveyard - Spoke with Ralis after changing scene (before obtaining earrings)", "Kakariko Village" }, + { 0x2F, 0x01, 0x2F01, "F_0389", "First conversation with goron digging hole in south", "Ring field" }, + { 0x30, 0x80, 0x3080, "F_0390", "First conversation after saving Jovani", "Castle Town" }, + { 0x30, 0x40, 0x3040, "F_0391", "Gave spring water to goron south of castle town", "Ring field" }, + { 0x30, 0x20, 0x3020, "F_0392", "spoke with goron digging hole after opening caslt town south road", "Ring field" }, + { 0x30, 0x10, 0x3010, "F_0393", "First conversation at Poe shop (generic Poe appearance)", "Castle Town" }, + { 0x30, 0x08, 0x3008, "F_0394", "Received first key from chibi elder (204)", "LV2 Dungeon" }, + { 0x30, 0x04, 0x3004, "F_0395", "First conversation with Gor Liggs in Kakariko Village", "Kakariko Village" }, + { 0x30, 0x02, 0x3002, "F_0396", "Failed to carry hot spring water (speak with elder to reset)", "Kakariko Village" }, + { 0x30, 0x01, 0x3001, "F_0397", "Gor Liggs conversation sequence B", "Kakariko Village" }, + { 0x31, 0x80, 0x3180, "F_0398", "Gor Liggs conversation sequence C", "Kakariko Village" }, + { 0x31, 0x40, 0x3140, "F_0399", "Conversation with Gor Liggs - hot spring game available", "Kakariko Village" }, + { 0x31, 0x20, 0x3120, "F_0400", "Warped sky cannon to Lake Hylia", "Kakariko Village" }, + { 0x31, 0x10, 0x3110, "F_0401", "Beetle (M)", "Golden bugs" }, + { 0x31, 0x08, 0x3108, "F_0402", "Beetle (F)", "Golden bugs" }, + { 0x31, 0x04, 0x3104, "F_0403", "Butterfly (M)", "Golden bugs" }, + { 0x31, 0x02, 0x3102, "F_0404", "Butterfly (F)", "Golden bugs" }, + { 0x31, 0x01, 0x3101, "F_0405", "Stag beetle (M)", "Golden bugs" }, + { 0x32, 0x80, 0x3280, "F_0406", "Stag beetle (F)", "Golden bugs" }, + { 0x32, 0x40, 0x3240, "F_0407", "Grasshopper (M)", "Golden bugs" }, + { 0x32, 0x20, 0x3220, "F_0408", "Grasshopper (F)", "Golden bugs" }, + { 0x32, 0x10, 0x3210, "F_0409", "Phasmid (M)", "Golden bugs" }, + { 0x32, 0x08, 0x3208, "F_0410", "Phasmid (F)", "Golden bugs" }, + { 0x32, 0x04, 0x3204, "F_0411", "Pill bug (M)", "Golden bugs" }, + { 0x32, 0x02, 0x3202, "F_0412", "Pill bug (F)", "Golden bugs" }, + { 0x32, 0x01, 0x3201, "F_0413", "Mantis (M)", "Golden bugs" }, + { 0x33, 0x80, 0x3380, "F_0414", "Mantis (F)", "Golden bugs" }, + { 0x33, 0x40, 0x3340, "F_0415", "Ladybug (M)", "Golden bugs" }, + { 0x33, 0x20, 0x3320, "F_0416", "Ladybug (F)", "Golden bugs" }, + { 0x33, 0x10, 0x3310, "F_0417", "Snail (M)", "Golden bugs" }, + { 0x33, 0x08, 0x3308, "F_0418", "Snail (F)", "Golden bugs" }, + { 0x33, 0x04, 0x3304, "F_0419", "Dragonfly (M)", "Golden bugs" }, + { 0x33, 0x02, 0x3302, "F_0420", "Dragonfly (F)", "Golden bugs" }, + { 0x33, 0x01, 0x3301, "F_0421", "Ant (M)", "Golden bugs" }, + { 0x34, 0x80, 0x3480, "F_0422", "Ant (F)", "Golden bugs" }, + { 0x34, 0x40, 0x3440, "F_0423", "Dayfly (M)", "Golden bugs" }, + { 0x34, 0x20, 0x3420, "F_0424", "Dayfly (F)", "Golden bugs" }, + { 0x34, 0x10, 0x3410, "F_0425", "[Captured in bottle] Beetle (M)", "Golden bugs" }, + { 0x34, 0x08, 0x3408, "F_0426", "[Captured in bottle] Beetle (F)", "Golden bugs" }, + { 0x34, 0x04, 0x3404, "F_0427", "[Captured in bottle] Butterfly (M)", "Golden bugs" }, + { 0x34, 0x02, 0x3402, "F_0428", "[Captured in bottle] Butterfly (F)", "Golden bugs" }, + { 0x34, 0x01, 0x3401, "F_0429", "[Captured in bottle] Stag beetle (M)", "Golden bugs" }, + { 0x35, 0x80, 0x3580, "F_0430", "[Captured in bottle] Stag beetle (F)", "Golden bugs" }, + { 0x35, 0x40, 0x3540, "F_0431", "[Captured in bottle] Grasshopper (M)", "Golden bugs" }, + { 0x35, 0x20, 0x3520, "F_0432", "[Captured in bottle] Grasshopper (F)", "Golden bugs" }, + { 0x35, 0x10, 0x3510, "F_0433", "[Captured in bottle] Phasmid (M)", "Golden bugs" }, + { 0x35, 0x08, 0x3508, "F_0434", "[Captured in bottle] Phasmid (F)", "Golden bugs" }, + { 0x35, 0x04, 0x3504, "F_0435", "[Captured in bottle] Pill bug (M)", "Golden bugs" }, + { 0x35, 0x02, 0x3502, "F_0436", "[Captured in bottle] Pill bug (F)", "Golden bugs" }, + { 0x35, 0x01, 0x3501, "F_0437", "[Captured in bottle] Mantis (M)", "Golden bugs" }, + { 0x36, 0x80, 0x3680, "F_0438", "[Captured in bottle] Mantis (F)", "Golden bugs" }, + { 0x36, 0x40, 0x3640, "F_0439", "[Captured in bottle] Ladybug (M)", "Golden bugs" }, + { 0x36, 0x20, 0x3620, "F_0440", "[Captured in bottle] Ladybug (F)", "Golden bugs" }, + { 0x36, 0x10, 0x3610, "F_0441", "[Captured in bottle] Snail (M)", "Golden bugs" }, + { 0x36, 0x08, 0x3608, "F_0442", "[Captured in bottle] Snail (F)", "Golden bugs" }, + { 0x36, 0x04, 0x3604, "F_0443", "[Captured in bottle] Dragonfly (M)", "Golden bugs" }, + { 0x36, 0x02, 0x3602, "F_0444", "[Captured in bottle] Dragonfly (F)", "Golden bugs" }, + { 0x36, 0x01, 0x3601, "F_0445", "[Captured in bottle] Ant (M)", "Golden bugs" }, + { 0x37, 0x80, 0x3780, "F_0446", "[Captured in bottle] Ant (F)", "Golden bugs" }, + { 0x37, 0x40, 0x3740, "F_0447", "[Captured in bottle] Dayfly (M)", "Golden bugs" }, + { 0x37, 0x20, 0x3720, "F_0448", "[Captured in bottle] Dayfly (F)", "Golden bugs" }, + { 0x37, 0x10, 0x3710, "F_0449", "Talk with Gor Liggs again - first time after talking iwth Hot spring water parent goron (before fundraising)", "Kakariko Village" }, + { 0x37, 0x08, 0x3708, "F_0450", "Double Clawshot shop final stage first conversation", "Castle Town" }, + { 0x37, 0x04, 0x3704, "F_0453", "Postman first appears", "Ring field" }, + { 0x37, 0x02, 0x3702, "F_0454", "Recieved 3rd key from fundraising elder (206)", "LV2 Dungeon" }, + { 0x37, 0x01, 0x3701, "F_0455", "Recieved 3rd key from fundraising elder (205)", "LV2 Dungeon" }, + { 0x38, 0x80, 0x3880, "F_0456", "First time meeting with Jovani", "Castle Town" }, + { 0x38, 0x40, 0x3840, "F_0457", "Revived cat", "Castle Town" }, + { 0x38, 0x20, 0x3820, "F_0458", "Coversation with Jovani after collecting 60 ghosts", "Castle Town" }, + { 0x38, 0x10, 0x3810, "F_0459", "Coversation with Gengle after collecting 40 ghosts", "Castle Town" }, + { 0x38, 0x08, 0x3808, "F_0460", "Coversation with Gengle after collecting 50 ghosts", "Castle Town" }, + { 0x38, 0x04, 0x3804, "F_0461", "First time entered fishing house", "Fishing" }, + { 0x38, 0x02, 0x3802, "F_0462", "Reserved for fishing", "Fishing" }, + { 0x38, 0x01, 0x3801, "F_0463", "Reserved for fishing", "Fishing" }, + { 0x39, 0x80, 0x3980, "F_0464", "Reserved for fishing", "Fishing" }, + { 0x39, 0x40, 0x3940, "F_0465", "Reserved for fishing", "Fishing" }, + { 0x39, 0x20, 0x3920, "F_0466", "Reserved for fishing", "Fishing" }, + //{ 0x39, 0x10, 0x3910, "F_0467", "N/A", "Fishing" }, + { 0x39, 0x08, 0x3908, "F_0468", "Reserved for fishing", "Fishing" }, + { 0x39, 0x04, 0x3904, "F_0469", "Reserved for fishing", "Fishing" }, + { 0x39, 0x02, 0x3902, "F_0470", "Reserved for fishing", "Fishing" }, + { 0x39, 0x01, 0x3901, "F_0471", "Finished 2 wrestling matches against Bo", "Ordon Village" }, + { 0x3A, 0x80, 0x3A80, "F_0472", "Distant howling complete (for secret technique 2)", "Secret techniques" }, + { 0x3A, 0x40, 0x3A40, "F_0473", "Distant howling complete (for secret technique 3)", "Secret techniques" }, + { 0x3A, 0x20, 0x3A20, "F_0474", "Distant howling complete (for secret technique 4)", "Secret techniques" }, + { 0x3A, 0x10, 0x3A10, "F_0475", "Distant howling complete (for secret technique 5)", "Secret techniques" }, + { 0x3A, 0x08, 0x3A08, "F_0476", "Distant howling complete (for secret technique 6)", "Secret techniques" }, + { 0x3A, 0x04, 0x3A04, "F_0477", "Distant howling complete (for secret technique 7)", "Secret techniques" }, + { 0x3A, 0x02, 0x3A02, "F_0478", "Spoke to Ralis who returned to Zora's domain", "Zora's Domain" }, + { 0x3A, 0x01, 0x3A01, "F_0479", "First conversation with Ralis in front of grave", "Kakariko Village" }, + { 0x3B, 0x80, 0x3B80, "F_0480", "Received Coral Earrings from Ralis", "Kakariko Village" }, + { 0x3B, 0x40, 0x3B40, "F_0481", "Beat Yeta at snowboard first time", "Snowpeak mountain" }, + { 0x3B, 0x20, 0x3B20, "F_0482", "After beating Yeto, first conversation with Yeta at peack (challanged to a match)", "N/A" }, + { 0x3B, 0x10, 0x3B10, "F_0483", "First beat Yeta at snowboard (heart piece)", "Snowpeak mountain" }, + { 0x3B, 0x08, 0x3B08, "F_0484", "Completed sky canon repairs!", "Lake Hylia" }, + { 0x3B, 0x04, 0x3B04, "F_0485", "Heard Fyer talk about repairs 1 time", "Lake Hylia" }, + { 0x3B, 0x02, 0x3B02, "F_0486", "Spoke with Luda while Ralis is visiting graveyard", "Kakariko Village" }, + { 0x3B, 0x01, 0x3B01, "F_0487", "Spoke with Luda after Ralis returns home", "Kakariko Village" }, + { 0x3C, 0x80, 0x3C80, "F_0488", "Conversation with goron in front of bomb shop - underwater bomb appears", "Kakariko Village" }, + { 0x3C, 0x40, 0x3C40, "F_0489", "Conversation with goron in front of bomb shop - all bomb types appear", "Kakariko Village" }, + { 0x3C, 0x20, 0x3C20, "F_0490", "Spoke with cucco (thinks Link will eat him)", "Ordon Village" }, + { 0x3C, 0x10, 0x3C10, "F_0491", "Spoke with mini-boss (magnet goron) after LV2 dungeon clear", "LV2 Dungeon" }, + { 0x3C, 0x08, 0x3C08, "F_0492", "Gold wolf disappearance 2", "Secret techniques" }, + { 0x3C, 0x04, 0x3C04, "F_0493", "Gold wolf disappearance 3", "Secret techniques" }, + { 0x3C, 0x02, 0x3C02, "F_0494", "Gold wolf disappearance 4", "Secret techniques" }, + { 0x3C, 0x01, 0x3C01, "F_0495", "Gold wolf disappearance 5", "Secret techniques" }, + { 0x3D, 0x80, 0x3D80, "F_0496", "Gold wolf disappearance 6", "Secret techniques" }, + { 0x3D, 0x40, 0x3D40, "F_0497", "Gold wolf disappearance 7", "Secret techniques" }, + { 0x3D, 0x20, 0x3D20, "F_0498", "Spoke with Beth after Lalis is revived", "Kakariko Village" }, + { 0x3D, 0x10, 0x3D10, "F_0499", "Saved magma goron", "Zora's Domain" }, + { 0x3D, 0x08, 0x3D08, "F_0500", "Met Uli before finding kids (look at sleeping Rusl)", "Ordon Village" }, + { 0x3D, 0x04, 0x3D04, "F_0501", "Cave of Ordeals - B10 first arrival", "sub-dungeon" }, + { 0x3D, 0x02, 0x3D02, "F_0502", "Cave of Ordeals - B20 first arrival", "sub-dungeon" }, + { 0x3D, 0x01, 0x3D01, "F_0503", "Cave of Ordeals - B30 first arrival", "sub-dungeon" }, + { 0x3E, 0x80, 0x3E80, "F_0504", "Cave of Ordeals - B40 first arrival", "sub-dungeon" }, + { 0x3E, 0x40, 0x3E40, "F_0505", "Cave of Ordeals - B50 first arrival (clear)", "sub-dungeon" }, + { 0x3E, 0x20, 0x3E20, "F_0506", "Only met Ooccoo Sr. - not yet son (shared LV1-LV5)", "N/A" }, + { 0x3E, 0x10, 0x3E10, "F_0507", "Also met the son in dungeon with first Ooccoo Sr. meeting - turns 506 OFF (shared LV1-LV5)", "N/A" }, + { 0x3E, 0x08, 0x3E08, "F_0508", "Meb Ooccoo Sr. B - doesnt turn OFF (shared LV1-LV5)", "Ooccoo Sr. stuff" }, + { 0x3E, 0x04, 0x3E04, "F_0509", "Met Ooccoo Sr. second time", "Ooccoo Sr. stuff" }, + { 0x3E, 0x02, 0x3E02, "F_0510", "Watched LV7 dungion start cutscene", "LV7 dungeon" }, + { 0x3E, 0x01, 0x3E01, "F_0513", "Spoke with Colin when neither Ilia nor Ralis are doing well", "Kakariko Village" }, + { 0x3F, 0x80, 0x3F80, "F_0514", "Spoke with Colin after Ralis is revived but before Ilia is revived", "Kakariko Village" }, + { 0x3F, 0x40, 0x3F40, "F_0515", "Spoke with Colin after Ilia and Ralis are revived", "Kakariko Village" }, + { 0x3F, 0x20, 0x3F20, "F_0516", "Told Uli directly about having found kids", "Ordon Village" }, + { 0x3F, 0x10, 0x3F10, "F_0517", "Had normal conversation 1 with Uli after finding kids (before Colin kidnapped)", "Ordon Village" }, + { 0x3F, 0x08, 0x3F08, "F_0518", "Colin kidnapped ~ Heard about giving letter to Colin from sleeping Uli", "Ordon Village" }, + { 0x3F, 0x04, 0x3F04, "F_0519", "Spoke with Juggle after finding children", "Ordon Village" }, + { 0x3F, 0x02, 0x3F02, "F_0520", "Spoke with Sera after finding children", "Ordon Village" }, + { 0x3F, 0x01, 0x3F01, "F_0521", "Spoke with Sera across the counter after finding children (her script is shortened after this)", "Ordon Village" }, + { 0x40, 0x80, 0x4080, "F_0522", "Heard about Sacred Grove from saved monkey girl", "Faron Woods" }, + { 0x40, 0x40, 0x4040, "F_0523", "Spoke with Hanch before finding children", "Ordon Village" }, + { 0x40, 0x20, 0x4020, "F_0524", "Spoke with Hanch after finding children", "Ordon Village" }, + { 0x40, 0x10, 0x4010, "F_0525", "First conversation with underwater Zora bomb seller", "Lake Hylia" }, + { 0x40, 0x08, 0x4008, "F_0526", "[Cutscene: 23] Midna reveals her true form", "Kawagoe Cutscene" }, + { 0x40, 0x04, 0x4004, "F_0527", "Refused Resistance Rusl's request", "Faron Woods" }, + { 0x40, 0x02, 0x4002, "F_0528", "Resistance Rusl summoned golden cucco", "Faron Woods" }, + { 0x40, 0x01, 0x4001, "F_0529", "Spoke with Rusl after clearing LV6 dungion", "Castle Town" }, + { 0x41, 0x80, 0x4180, "F_0530", "Spoke with Pergie after finding children", "Ordon Village" }, + { 0x41, 0x40, 0x4140, "F_0531", "Spoke with Pergie while children are kidnapped", "Ordon Village" }, + { 0x41, 0x20, 0x4120, "F_0532", "Pergie butts in about a shield", "Ordon Village" }, + { 0x41, 0x10, 0x4110, "F_0533", "Spoke with Fado after finding children", "Ordon Village" }, + { 0x41, 0x08, 0x4108, "F_0534", "Spoke with Fado before finding children", "Ordon Village" }, + { 0x41, 0x04, 0x4104, "F_0535", "Spoke with goron in water after recieving reward (bomb bag)", "Zora's Domain" }, + { 0x41, 0x02, 0x4102, "F_0536", "Spoke with goron in water before recieving reward", "Zora's Domain" }, + { 0x41, 0x01, 0x4101, "F_0537", "Had Coro and wolf conversation", "Faron Woods" }, + { 0x42, 0x80, 0x4280, "F_0538", "Spoke with sky person Oocoo Sr. after clearing LV7 dungeon", "LV7 dungeon" }, + { 0x42, 0x40, 0x4240, "F_0539", "Receieved heart piece from Fado for mountain goat chase", "Ordon Village" }, + { 0x42, 0x20, 0x4220, "F_0540", "Heard hint from Midna right after first portal warp", "Faron Woods" }, + //{ 0x42, 0x10, 0x4210, "F_0541", "N/A", "N/A" }, + { 0x42, 0x08, 0x4208, "F_0542", "[Cutscene] Ganon wall disappears (Midna goes crazy)", "Kawagoe Cutscene" }, + { 0x42, 0x04, 0x4204, "F_0543", "Met Resistance Rusl again in the woods", "Faron Woods" }, + { 0x42, 0x02, 0x4202, "F_0544", "Watched cutscene of monkey girl running away after being attacked by puppet", "Faron Woods" }, + { 0x42, 0x01, 0x4201, "F_0545", "Watched cutscene of Ooccoo Sr. parting (after LV6)", "Sacred Grove" }, + { 0x43, 0x80, 0x4380, "F_0546", "East - Spoke with soldier in front of east gate about light (while bridge is broken)", "Castle Town" }, + { 0x43, 0x40, 0x4340, "F_0547", "conversation with Jovani after collecting 40 ghosts", "Castle Town" }, + { 0x43, 0x20, 0x4320, "F_0548", "Opening 3rd day - spoke with Jaggle", "Ordon Village" }, + { 0x43, 0x10, 0x4310, "F_0549", "Heard old lady A, B, soldier set (talking about water) (when there's no water)", "Castle Town" }, + { 0x43, 0x08, 0x4308, "F_0550", "Gain ability to use sense", "main event" }, + { 0x43, 0x04, 0x4304, "F_0551", "LV8 Dungeon control use 1", "LV8 Dungeon" }, + { 0x43, 0x02, 0x4302, "F_0552", "LV8 Dungeon control use 2", "LV8 Dungeon" }, + { 0x43, 0x01, 0x4301, "F_0553", "LV8 Dungeon control use 3", "LV8 Dungeon" }, + { 0x44, 0x80, 0x4480, "F_0554", "LV8 Dungeon control use 4", "LV8 Dungeon" }, + { 0x44, 0x40, 0x4440, "F_0555", "LV8 Dungeon control use 5", "LV8 Dungeon" }, + { 0x44, 0x20, 0x4420, "F_0556", "LV8 Dungeon control use 6", "LV8 Dungeon" }, + { 0x44, 0x10, 0x4410, "F_0557", "LV8 Dungeon control use 7", "LV8 Dungeon" }, + { 0x44, 0x08, 0x4408, "F_0558", "LV8 Dungeon control use 8", "LV8 Dungeon" }, + { 0x44, 0x04, 0x4404, "F_0559", "LV8 Dungeon control use 9", "LV8 Dungeon" }, + { 0x44, 0x02, 0x4402, "F_0560", "LV8 Dungeon control use 10", "LV8 Dungeon" }, + { 0x44, 0x01, 0x4401, "F_0561", "LV8 Dungeon control use 11", "LV8 Dungeon" }, + { 0x45, 0x80, 0x4580, "F_0562", "LV8 Dungeon control use 12", "LV8 Dungeon" }, + { 0x45, 0x40, 0x4540, "F_0563", "Brought back Gengle (after getting 50 souls, dealt with by program)", "Misc." }, + { 0x45, 0x20, 0x4520, "F_0564", "Heard story from solder at Telma's shop (during Twilight)", "Castle Town" }, + { 0x45, 0x10, 0x4510, "F_0565", "2nd Day Complete", "N/A" }, + { 0x45, 0x08, 0x4508, "F_0566", "Letter from Ooccoo Sr. came in OK (no son)", "Ooccoo Sr. stuff" }, + { 0x45, 0x04, 0x4504, "F_0567", "Letter from Ooccoo Sr. came in OK (yes son)", "Ooccoo Sr. stuff" }, + { 0x45, 0x02, 0x4502, "F_0568", "Broke Iza's pots (first offence)", "Zora's River" }, + { 0x45, 0x01, 0x4501, "F_0569", "Compensation demands from Iza (resets if paid)", "Zora's River" }, + { 0x46, 0x80, 0x4680, "F_0570", "Cleared LV8 dungeon", "LV8 Dungeon" }, + { 0x46, 0x40, 0x4640, "F_0571", "Refuse Talo's request (for wooden sword) on 3rd day (first time)", "Ordon Village" }, + { 0x46, 0x20, 0x4620, "F_0572", "Refuse Talo's request (for wooden sword) on 3rd day (again)", "Ordon Village" }, + { 0x46, 0x10, 0x4610, "F_0573", "1st Day - Fado moves to the farm (disappears from in front of Link's house)", "Ordon Village" }, + { 0x46, 0x08, 0x4608, "F_0574", "1st Day - Start mountain goat chasing in evening", "Ranch" }, + { 0x46, 0x04, 0x4604, "F_0575", "1st Day - End mountain goat chasing in evening", "Ranch" }, + { 0x46, 0x02, 0x4602, "F_0576", "2nd Day - Spoke with Fado", "Ordon Village" }, + { 0x46, 0x01, 0x4601, "F_0577", "2nd Day - Retrieved basket from monkey (hit hawk)", "Ordon Village" }, + { 0x47, 0x80, 0x4780, "F_0578", "Spoke to Beth on 3rd day", "Ordon Village" }, + { 0x47, 0x40, 0x4740, "F_0579", "Opening (2nd day) First time talking with Hanch before being attacked by bees", "Ordon Village" }, + { 0x47, 0x20, 0x4720, "F_0580", "1st Day - Blew on Epona's reed whistle at Ordon Spring", "Ordon Village" }, + { 0x47, 0x10, 0x4710, "F_0581", "2nd Day - First time calling the hawk using the grass whistle", "Ordon Village" }, + { 0x47, 0x08, 0x4708, "F_0582", "1st Day - spoke to Ilia before blowing the reed whistle", "Ordon Woods" }, + { 0x47, 0x04, 0x4704, "F_0583", "1st day - Spoke to Ilia after blowing the reed whistle", "Ordon Woods" }, + { 0x47, 0x02, 0x4702, "F_0584", "1st Day - spoke to Rusl", "Ordon Village" }, + { 0x47, 0x01, 0x4701, "F_0585", "1st day - Spoke to Bo", "Ordon Village" }, + { 0x48, 0x80, 0x4880, "F_0586", "Spoke to Bo after defeating mountain goat", "Ordon Village" }, + { 0x48, 0x40, 0x4840, "F_0587", "successfully defeated rampaging mountain goat", "Ordon Village" }, + { 0x48, 0x20, 0x4820, "F_0588", "First time letting rampaging mountain goat escape", "Ordon Village" }, + { 0x48, 0x10, 0x4810, "F_0589", "Sera - spoke on first day", "Ordon Village" }, + { 0x48, 0x08, 0x4808, "F_0590", "2nd Day - Spoke to Malo during Talo's disappearance (after getting lantern)", "Ordon Village" }, + { 0x48, 0x04, 0x4804, "F_0591", "2nd Day - Spoke to Malo right after Talo disappears", "Ordon Woods" }, + { 0x48, 0x02, 0x4802, "F_0592", "2nd Day - Spoke with Beth during Talo's disappearance (after getting lantern)", "Ordon Village" }, + { 0x48, 0x01, 0x4801, "F_0593", "2nd Day - Conversation with Beth right after Talo's disappearance", "Ordon Woods" }, + { 0x49, 0x80, 0x4980, "F_0594", "2nd Day - Colin (conversation after receiving lantern from Coro)", "Ordon Village" }, + { 0x49, 0x40, 0x4940, "F_0595", "2nd Day - Conversation with Colin after sword tutorial and after horse rejection", "Ordon Village" }, + { 0x49, 0x20, 0x4920, "F_0596", "2nd Day - Conversation with Colin after sword tutorial, before horse rejection", "Ordon Village" }, + { 0x49, 0x10, 0x4910, "F_0597", "2nd Day - First time speaking with Bo (after successfully defeating mountain goat)", "Ordon Village" }, + { 0x49, 0x08, 0x4908, "F_0598", "2nd Day - First conversation with Bo", "Ordon Village" }, + { 0x49, 0x04, 0x4904, "F_0599", "2nd Day - tried to enter Bo's house", "Ordon Village" }, + { 0x49, 0x02, 0x4902, "F_0600", "Purchase slingshot", "Ordon Village" }, + { 0x49, 0x01, 0x4901, "F_0601", "Spoke to imprisoned Talo", "Faron Woods" }, + { 0x4A, 0x40, 0x4A40, "F_0700", "First day ends", "N/A" }, + { 0x4A, 0x20, 0x4A20, "F_0701", "Talo discovers monkey in Link's house garden", "N/A" }, + { 0x4A, 0x10, 0x4A10, "F_0702", "Confirmed kidnapped Talo", "Ordon Village" }, + { 0x4A, 0x08, 0x4A08, "F_0606", "Giants switched places", "Sacred Grove" }, + { 0x4A, 0x04, 0x4A04, "F_0607", "Quit Slingshot tutorial", "Ordon Village" }, + { 0x4A, 0x02, 0x4A02, "F_0608", "Began Slingshot tutorial", "Ordon Village" }, + { 0x4A, 0x01, 0x4A01, "F_0609", "Slingshot Tutorial - First time hitting scarcrow torso", "Ordon Village" }, + { 0x4B, 0x80, 0x4B80, "F_0610", "Slingshot Tutorial - Hit it without using focus", "Ordon Village" }, + { 0x4B, 0x40, 0x4B40, "F_0611", "Slingshot Tutorial - Hit it using focusing", "Ordon Village" }, + { 0x4B, 0x20, 0x4B20, "F_0612", "Pachinco Tutorial - Spoke before hitting target", "Ordon Village" }, + { 0x4B, 0x10, 0x4B10, "F_0613", "Slingshot Tutorial - Spoke before getting 2 scarecrow heards", "Ordon Village" }, + { 0x4B, 0x08, 0x4B08, "F_0614", "2nd Day - Heard forced conversation immediately after the slignshot tutorial", "Ordon Village" }, + { 0x4B, 0x04, 0x4B04, "F_0615", "Recieved vessel of light from Lanayru spirit", "Lake Hylia" }, + { 0x4B, 0x02, 0x4B02, "F_0616", "3rd Day - Spoke to Talo/Malo after handing over wooden sword (1st time)", "Ordon Village" }, + { 0x4B, 0x01, 0x4B01, "F_0617", "3rd Day - Spoke to Talo/Malo after handing over wooden sword (2nd time)", "Ordon Village" }, + { 0x4C, 0x80, 0x4C80, "F_0618", "Scooped bee larva into bottle on opening 2nd Day ", "Ordon Village" }, + { 0x4C, 0x40, 0x4C40, "F_0619", "Spoke to Zora soldier (near cannon) in Lake Hylia", "Lake Hylia" }, + { 0x4C, 0x20, 0x4C20, "F_0620", "First caught a Reek Fish", "Zora's Domain" }, + { 0x4C, 0x10, 0x4C10, "F_0621", "Spoke to Hanch on 3rd day (knocked down beehive on 2nd day)", "Ordon Village" }, + { 0x4C, 0x08, 0x4C08, "F_0622", "Opening 2nd day: Spoke to Hanch before being attacked by bees", "Ordon Village" }, + { 0x4C, 0x04, 0x4C04, "F_0623", "Opening 3rd day: Spoke to Hanch before being attacked by bees", "Ordon Village" }, + { 0x4C, 0x02, 0x4C02, "F_0624", "3rd day: Warned by Hanch when climbing vines", "Ordon Village" }, + { 0x4C, 0x01, 0x4C01, "F_0625", "Saved Talo and a monkey", "Faron Woods" }, + { 0x4D, 0x80, 0x4D80, "F_0626", "Received a heart piece from Jovani", "Castle Town" }, + { 0x4D, 0x40, 0x4D40, "F_0627", "Spoke with Jovani at the bar", "Castle Town" }, + { 0x4D, 0x20, 0x4D20, "F_0628", "Received 200 Rupees from Gengle", "Castle Town" }, + { 0x4D, 0x10, 0x4D10, "F_0629", "First conversation iwth Gengle after speaking with Jovani at the bar", "Castle Town" }, + { 0x4D, 0x08, 0x4D08, "F_0630", "(Cutscene 4 - ?) Right after Link is captured (wolf)", "Kawagoe Cutscene" }, + { 0x4A, 0x80, 0x4A80, "F_0631", "Heard when zooming in on fish tank (1)", "Fishing Pond (inside)" }, + { 0x4D, 0x04, 0x4D04, "F_0632", "Heard first time zooming in on fish tank (2)", "Fishing Pond (inside)" }, + { 0x4D, 0x02, 0x4D02, "F_0633", "Heard second time zooming in on fish tank (2)", "Fishing Pond (inside)" }, + { 0x4D, 0x01, 0x4D01, "F_0634", "Heard zooming in on lure (no frog)", "Fishing Pond (inside)" }, + { 0x4E, 0x80, 0x4E80, "F_0635", "Heard zooming in on lure (yes frog)", "Fishing Pond (inside)" }, + { 0x4E, 0x40, 0x4E40, "F_0636", "Heard first time zooming in on frog lure", "Fishing Pond (inside)" }, + { 0x4E, 0x20, 0x4E20, "F_0637", "Cleard all of roll goal game (get frog lure)", "Fishing Pond (inside)" }, + { 0x4E, 0x10, 0x4E10, "F_0638", "Heard first time zooming in on canoe", "Fishing Pond (inside)" }, + { 0x4E, 0x08, 0x4E08, "F_0639", "Heard first time zooming in on hat", "Fishing Pond (inside)" }, + { 0x4E, 0x04, 0x4E04, "F_0640", "Heard first time zooming in on pot", "Fishing Pond (inside)" }, + { 0x4E, 0x02, 0x4E02, "F_0641", "Heard First time zooming in on rug", "Fishing Pond (inside)" }, + { 0x4E, 0x01, 0x4E01, "F_0642", "Heard first time zooming in on book", "Fishing Pond (inside)" }, + { 0x4F, 0x80, 0x4F80, "F_0643", "Heard first time zooming in on old man's photo", "Fishing Pond (inside)" }, + { 0x4F, 0x40, 0x4F40, "F_0644", "Heard first time zooming in on Coro's photo", "Fishing Pond (inside)" }, + { 0x4F, 0x20, 0x4F20, "F_0645", "Heard First time zoomin in on Iza's photo", "Fishing Pond (inside)" }, + { 0x4F, 0x10, 0x4F10, "F_0646", "Heard first time zooming in on Hena's photo", "Fishing Pond (inside)" }, + { 0x4F, 0x08, 0x4F08, "F_0647", "Heard second time zooming in on Hena's photo", "Fishing Pond (inside)" }, + { 0x4F, 0x04, 0x4F04, "F_0648", "Heard Hena's photo 1,2 (additional story)", "Fishing Pond (inside)" }, + { 0x4F, 0x02, 0x4F02, "F_0649", "Heard first time zooming in on Link's picture", "Fishing Pond (inside)" }, + { 0x4F, 0x01, 0x4F01, "F_0650", "Heard second time zooming in on Link's picture", "Fishing Pond (inside)" }, + { 0x50, 0x80, 0x5080, "F_0651", "Caught Greengill with bobber first time", "Fishing" }, + { 0x50, 0x40, 0x5040, "F_0652", "Caught Hylian Bass with bobber first time", "Fishing" }, + { 0x50, 0x20, 0x5020, "F_0653", "Caught Hylian Pike with bobber first time", "Fishing" }, + { 0x50, 0x10, 0x5010, "F_0654", "Caught Hylian Loach using bobber first time", "Fishing" }, + { 0x50, 0x08, 0x5008, "F_0655", "Caught an Ordon Catfish using bobber first time", "Fishing" }, + { 0x50, 0x04, 0x5004, "F_0656", "Caught something (any fish) using lure first time", "Fishing" }, + { 0x50, 0x02, 0x5002, "F_0657", "Caught Hylian Loach in front of Hena first time", "Fishing" }, + { 0x50, 0x01, 0x5001, "F_0658", "Recieved large wallet from Agitha", "Castle Town" }, + { 0x51, 0x80, 0x5180, "F_0659", "Hear information about spirit spring in Lake Hylia from Twilight Soldier or map", "Castle Town" }, + { 0x51, 0x40, 0x5140, "F_0660", "Twilight Lake Hylia Listened to Zora soldier A talk", "Lake Hylia" }, + { 0x51, 0x20, 0x5120, "F_0661", "Twilight Lake Hylia Listened to Zora soldier B&C talk", "Lake Hylia" }, + { 0x51, 0x10, 0x5110, "F_0662", "Omit rules after clearing roll goal game once", "Fishing Pond (inside)" }, + { 0x51, 0x08, 0x5108, "F_0663", "Listened to Purdy's unnecessary words", "Fishing Pond (inside)" }, + { 0x51, 0x04, 0x5104, "F_0664", "After clearing roll goal game / first attempt", "Fishing Pond (inside)" }, + { 0x51, 0x02, 0x5102, "F_0665", "First zoom in Roll goal game", "Fishing Pond (inside)" }, + { 0x51, 0x01, 0x5101, "F_0666", "Showed Yeto sketch to Zora soldier next to Snowpeak Mountain", "Zora's Domain" }, + { 0x52, 0x80, 0x5280, "F_0667", "Showed Yeto sketch to average Zora soldier (generic)", "Zora's Domain" }, + { 0x52, 0x40, 0x5240, "F_0668", "Showed Yeto sketch to Zora civilian (generic)", "Zora's Domain" }, + { 0x52, 0x20, 0x5220, "F_0669", "Pulled on Fyrus chains at least once", "LV2 Dungeon" }, + { 0x52, 0x10, 0x5210, "F_0670", "Hitting knocked-down Fyrus", "LV2 Dungeon" }, + { 0x52, 0x08, 0x5208, "F_0671", "Through magnet goron into lava once", "LV2 Dungeon" }, + { 0x52, 0x04, 0x5204, "F_0672", "First time seeing magnet goron tumbling", "LV2 Dungeon" }, + { 0x52, 0x02, 0x5202, "F_0673", "Heard hint about Fyrus's weakness (F0215)", "LV2 Dungeon" }, + { 0x52, 0x01, 0x5201, "F_0674", "Light - Spoke with cafe table: townsperson A1", "Castle Town" }, + { 0x53, 0x80, 0x5380, "F_0675", "Light - cafe counter: Spoke with townsgirl A1 (shop clerk)", "Castle Town" }, + { 0x53, 0x40, 0x5340, "F_0676", "Light - Spoke with Cafe customer: Townsperson F1 (just the man)", "Castle Town" }, + { 0x53, 0x20, 0x5320, "F_0677", "Light - Spoke with Cafe customer B: Townsperson D1 and Man B1", "Castle Town" }, + { 0x53, 0x10, 0x5310, "F_0678", "Spoke with Group A (Town girl C2, Boy B1, Town girl D1) in front of fountain", "Castle Town" }, + { 0x53, 0x08, 0x5308, "F_0679", "Cheated during Roll goal game", "Fishing Pond (inside)" }, + { 0x53, 0x04, 0x5304, "F_0680", "Heard Midna's hint after cutscene of ghost escaping", "LV4 Dungeon," }, + { 0x53, 0x02, 0x5302, "F_0681", "Heard Midna's hint after defeating first ghost", "LV4 Dungeon," }, + { 0x53, 0x01, 0x5301, "F_0682", "First conversation with Oocca Shopkeeper", "LV7 dungeon" }, + { 0x54, 0x80, 0x5480, "F_0683", "Spoke with Group B (Old lady A1, Boy A1, Town girl B1) in central square", "Castle Town" }, + { 0x54, 0x40, 0x5440, "F_0684", "Look at R00 statue using sense", "LV6 Dungeon" }, + { 0x54, 0x20, 0x5420, "F_0685", "(Cutscene 32) Sage appears, get first Mirror of Twilight shard", "Kawagoe Cutscene" }, + { 0x54, 0x10, 0x5410, "F_0686", "Get fused shadow piece (final mask)", "LV8 Dungeon" }, + { 0x54, 0x08, 0x5408, "F_0687", "Spoke with town girl D1 in front of south road hot spring shop (CLOSED)", "Castle Town" }, + { 0x54, 0x04, 0x5404, "F_0688", "Spoke with town girl D1 in front of south road hot spring shop (OPEN)", "Castle Town" }, + { 0x54, 0x02, 0x5402, "F_0689", "South Road - Spoke with meat shop villager man C1", "Castle Town" }, + { 0x54, 0x01, 0x5401, "F_0690", "South Road - Spoke with woman A1 in front of fortune teller's shop", "Castle Town" }, + { 0x55, 0x80, 0x5580, "F_0691", "Spoke with south alley children before guarding carriage", "Castle Town" }, + { 0x55, 0x40, 0x5540, "F_0692", "Spoke with south alley children after guarding carriage", "Castle Town" }, + { 0x55, 0x20, 0x5520, "F_0693", "First conversation with Plumm while human", "Lake Hylia" }, + { 0x55, 0x10, 0x5510, "F_0694", "Conversation with Jovani after collecting 20 ghosts", "Castle Town" }, + { 0x55, 0x08, 0x5508, "F_0695", "First time frog lure slips out", "Fishing" }, + { 0x55, 0x04, 0x5504, "F_0696", "Spoke with inquiring Hyrule guard before saving Jovani", "Castle Town" }, + { 0x55, 0x02, 0x5502, "F_0697", "Spoke with inquiring Hyrule guard after saving Jovani", "N/A" }, + { 0x55, 0x01, 0x5501, "F_0698", "Spoke with woman B1 in front of south vegetable stand (before guarding carriage)", "Castle Town" }, + { 0x56, 0x80, 0x5680, "F_0699", "Spoke with woman B1 in front of south vegetable stand (after guarding carriage)", "Castle Town" }, + { 0x56, 0x40, 0x5640, "F_0700B", "South Vegetable vendor - Spoke with Villager D2 (before guarding carriage)", "Castle Town" }, + { 0x56, 0x20, 0x5620, "F_0701B", "South Vegetable vendor - Spoke with Villager D2 (after guarding carriage)", "N/A" }, + { 0x56, 0x10, 0x5610, "F_0702B", "South Baker - Spoke with Boy A2", "Castle Town" }, + { 0x56, 0x08, 0x5608, "F_0703", "Caught Reek fish for first time while fishing", "Fishing" }, + { 0x56, 0x04, 0x5604, "F_0704", "Listened to Iza in Twilight before domain is thawed", "Zora's River" }, + { 0x56, 0x02, 0x5602, "F_0705", "First conversation with Kaeru (frog) during Midna's desperate hour", "Castle Town" }, + { 0x56, 0x01, 0x5601, "F_0706", "First conversation wtih Torako (cat) during Midna's desperate hour", "Castle Town" }, + { 0x57, 0x80, 0x5780, "F_0707", "Midna's desperate hour, first conversation with Mii (cat)", "Castle Town" }, + { 0x57, 0x40, 0x5740, "F_0708", "First conversaton with doctor after completing spirit", "Castle Town" }, + { 0x57, 0x20, 0x5720, "F_0709", "Midna's desperate hour, first conversation wtih Stephanie (cat)", "Castle Town" }, + { 0x57, 0x10, 0x5710, "F_0715", "Speak with dog to west", "Castle Town" }, + { 0x57, 0x08, 0x5708, "F_0716", "Speak with dog to the east", "Castle Town" }, + { 0x57, 0x04, 0x5704, "F_0717", "Wist - Speak to the dog owner", "Castle Town" }, + { 0x57, 0x02, 0x5702, "F_0718", "South - Spoke to old lady at the vegetable stand (Chudley)", "Castle Town" }, + { 0x57, 0x01, 0x5701, "F_0719", "South - Spoke to old lady at the vegetable stand (Malo Mart)", "Castle Town" }, + { 0x58, 0x80, 0x5880, "F_0720", "South - Spoke to fruit stand vendor (before guarding carriage)", "Castle Town" }, + { 0x58, 0x40, 0x5840, "F_0721", "South - Spoke to fruit stand vendor (after guarding carriage)", "Castle Town" }, + { 0x58, 0x20, 0x5820, "F_0722", "East - Spoke to man at the T-shaped street", "Castle Town" }, + { 0x58, 0x10, 0x5810, "F_0723", "East - Spoke to man in the alley", "Castle Town" }, + { 0x58, 0x08, 0x5808, "F_0724", "East - Spoke to two ladies gossiping", "Castle Town" }, + { 0x58, 0x04, 0x5804, "F_0725", "Speak to Renado who is announcing Shad's visit", "Kakariko Village" }, + { 0x58, 0x02, 0x5802, "F_0726", "Talk to Iza as wolf", "Zora's River" }, + { 0x58, 0x01, 0x5801, "F_0727", "First conversation with shoe-shine boy", "Castle Town" }, + { 0x59, 0x80, 0x5980, "F_0728", "Refused entry into Chudley's shop because of dirty shoes", "Castle Town" }, + { 0x59, 0x40, 0x5940, "F_0729", "Right after postman's \"Hey!\"", "Ring field" }, + { 0x59, 0x20, 0x5920, "F_0730", "Rode Iza's boat for the first time", "Zora's River" }, + { 0x59, 0x10, 0x5910, "F_0732", "Spoke with Cucco (After completing Ilia's memory event)", "Hidden Village" }, + { 0x59, 0x08, 0x5908, "F_0733", "Recieved Heart piece reward from Iza for going down the river", "Zora's River" }, + { 0x59, 0x04, 0x5904, "F_0734", "Spoke with wild duck", "Fishing" }, + { 0x59, 0x02, 0x5902, "F_0735", "Spoke with domestic duck", "Fishing" }, + { 0x59, 0x01, 0x5901, "F_0736", "Heard Midna's hint / monkey hint", "LV1 Dungeon" }, + { 0x5A, 0x80, 0x5A80, "F_0737", "First conversation with Udo (cat) during Midna's desperate hour", "Castle Town" }, + { 0x5A, 0x40, 0x5A40, "F_0738", "South - Spoke with female customer at vegetable stand", "Castle Town" }, + { 0x5A, 0x20, 0x5A20, "F_0739", "South - Spoke with old customer at fruit stand", "Castle Town" }, + { 0x5A, 0x10, 0x5A10, "F_0740", "Spoke with child Goron (selling lantern oil) (before Ganon wall)", "Castle Town" }, + { 0x5A, 0x08, 0x5A08, "F_0741", "Spoke with child Goron (selling lantern oil) (after Ganon wall)", "Castle Town" }, + { 0x5A, 0x04, 0x5A04, "F_0742", "Spoke with child Goron (selling red potion)", "Castle Town" }, + { 0x5A, 0x02, 0x5A02, "F_0743", "Spoke with adult Goron (30 arrows) (before Ganon wall)", "Castle Town" }, + { 0x5A, 0x01, 0x5A01, "F_0744", "Spoke with adult Goron (30 arrows) (after Ganon wall)", "Castle Town" }, + { 0x5B, 0x80, 0x5B80, "F_0745", "Spoke with adult Goron (Hylia shield)", "Castle Town" }, + { 0x5B, 0x40, 0x5B40, "F_0746", "Spoke with Cucco A", "Kakariko Village" }, + { 0x5B, 0x20, 0x5B20, "F_0747", "Spoke with Cucco B", "Kakariko Village" }, + { 0x5B, 0x10, 0x5B10, "F_0748", "First conversation with hint hawk", "Ring field" }, + { 0x5B, 0x08, 0x5B08, "F_0749", "After clearing cat game (Heart piece is buried, whether it is obtained depends on the player)", "Hidden Village" }, + { 0x5B, 0x04, 0x5B04, "F_0750", "First conversation with Captain Cucco after beginning cat game activities", "Hidden Village" }, + { 0x5B, 0x02, 0x5B02, "F_0751", "Heard detailed explaination from Captain Cucco for the first time", "Hidden Village" }, + { 0x5B, 0x01, 0x5B01, "F_0752", "Beat cat game again", "Hidden Village" }, + { 0x5C, 0x80, 0x5C80, "F_0753", "Scooped Coro's bad soup for the first time", "Faron Woods" }, + { 0x5C, 0x40, 0x5C40, "F_0754", "Heard Midna's hint / Monkey hint 2 times (SAVE72=ON)", "LV1 Dungeon" }, + { 0x5C, 0x20, 0x5C20, "F_0755", "Twilight Heard Iza's line right after shadow bug appears", "Zora's River" }, + { 0x5C, 0x10, 0x5C10, "F_0756", "Join with Ooccoo Sr.", "LV7 dungeon" }, + { 0x5C, 0x08, 0x5C08, "F_0757", "Had Trill and wolf conversation", "Faron Woods" }, + { 0x5C, 0x04, 0x5C04, "F_0758", "Stole from unmanned shop (For use in Trill wolf conversation. Resets after speaking)", "Faron Woods" }, + { 0x5C, 0x02, 0x5C02, "F_0759", "Opening (3rd day) first conversation with Sera", "Ordon Village" }, + { 0x5C, 0x01, 0x5C01, "B_BTN_GUIDE", "B-button guide", "Misc." }, + { 0x5D, 0x80, 0x5D80, "J_BTN_GUIDE", "X-button guide", "Misc." }, + { 0x5D, 0x40, 0x5D40, "F_0768", "Heard forced conversation with Midna after Hylia Lake introduction cutscene", "Lake Hylia" }, + { 0x5D, 0x20, 0x5D20, "F_0769", "Forced conversation with Midna right after first arrival at upper Zora's river", "Zora's River" }, + { 0x5D, 0x10, 0x5D10, "F_0770", "Twilight (frozen) Heard forced conversation after first visit cutscene", "Zora's Domain" }, + { 0x5D, 0x08, 0x5D08, "F_0771", "Unmanned Shop : last payment stolen", "Faron Woods" }, + { 0x5D, 0x04, 0x5D04, "F_0772", "Unmanned Shop : last payment too little", "Faron Woods" }, + { 0x5D, 0x02, 0x5D02, "F_0773", "Unmanned Shop : last payment too much", "Faron Woods" }, + { 0x5D, 0x01, 0x5D01, "F_0774", "Listened to Midna's conversation after seeing frozen Zora", "Zora's Domain" }, + { 0x5E, 0x80, 0x5E80, "F_0775", "Heard forced conversation with Midna after first Fyer's cannon", "Lake Hylia" }, + { 0x5E, 0x40, 0x5E40, "F_0776", "Link first turned to wolf due to fog in LV8 dungeon", "LV8 Dungeon" }, + { 0x5E, 0x20, 0x5E20, "F_0777", "Spoke to Epona", "Misc." }, + { 0x5E, 0x10, 0x5E10, "F_0778", "Heard forced conversation with Midna after story about spirits after clearing LV1 dungeon", "Faron Woods" }, + { 0x5E, 0x08, 0x5E08, "F_0779", "Heard one hint from Midna while bridge is burning", "Lake Hylia" }, + { 0x5E, 0x04, 0x5E04, "F_0780", "Heard Ilia talk about the messenger from the sky after her memories are restored", "Kakariko Village" }, + { 0x5E, 0x02, 0x5E02, "F_0781", "Spoke to Ilia in church after clearing LV6", "Kakariko Village" }, + { 0x5E, 0x01, 0x5E01, "F_0782", "Spoke to Renado after LV3 complete, before LV6 complete", "Kakariko Village" }, + { 0x5F, 0x80, 0x5F80, "F_0783", "Showed dominion rod to Impaz", "Hidden Village" }, + { 0x5F, 0x40, 0x5F40, "F_0784", "Had normal conversation with Shad after he returns to church basement", "Kakariko Village" }, + { 0x5F, 0x20, 0x5F20, "F_0785", "Shad leaves after attempting to warp sky cannon", "Kakariko Village" }, + { 0x5F, 0x10, 0x5F10, "F_0786", "Forced conversation with Shad when he sees the sky cannon", "Kakariko Village" }, + { 0x5F, 0x08, 0x5F08, "F_0787", "Stopped by Midna when trying to warp the sky cannon", "Kakariko Village" }, + { 0x5F, 0x04, 0x5F04, "F_0788", "Talked to Zora going up the waterfall", "Zora's River" }, + { 0x5F, 0x02, 0x5F02, "F_0789", "Grabbed sweet apple 1 time", "Castle Town" }, + { 0x5F, 0x01, 0x5F01, "F_0790", "Sera faces the cat (outside conversation area when 520 is ON)", "Ordon Village" }, + { 0x60, 0x80, 0x6080, "F_0791", "Sky character 1", "Sky character" }, + { 0x60, 0x40, 0x6040, "F_0792", "Sky character 2", "Sky character" }, + { 0x60, 0x20, 0x6020, "F_0793", "Sky character 3", "Sky character" }, + { 0x60, 0x10, 0x6010, "F_0794", "Sky character 4", "Sky character" }, + { 0x60, 0x08, 0x6008, "F_0795", "Sky character 5", "Sky character" }, + { 0x60, 0x04, 0x6004, "F_0796", "Sky character 6", "Sky character" }, + { 0x60, 0x02, 0x6002, "F_0797", "Have selected \"Take Cannon / Repair\" at Fyer's cannon (when there's no other business)", "Lake Hylia" }, + { 0x60, 0x01, 0x6001, "F_0798", "Heard about Zora from Fyer", "Lake Hylia" }, + { 0x61, 0x80, 0x6180, "F_0799", "Listened to the fallen Goron's complaints (after opening bridge)", "Ring field" }, + { 0x61, 0x40, 0x6140, "F_0800", "After returning to Ordon Woods, until Midna comes out of the shadows (If 800 is ON, Midna can't be called)", "Kawagoe Cutscene" }, + { 0x61, 0x20, 0x6120, "F_0801", "First learned Reek Fish scent", "Zora's Domain" }, + { 0x61, 0x10, 0x6110, "F_0802", "Trill attacks when stealing", "Faron Woods" }, + { 0x61, 0x08, 0x6108, "F_0803", "Talked to Old Lady Impaz after going to Sky", "Hidden Village" }, + { 0x61, 0x04, 0x6104, "F_0804", "Heard Midna's forced conversation immediately after plunge into Eldin Twilight", "Twilight Ring field" }, + { 0x61, 0x02, 0x6102, "F_0805", "Bought Hylian shield from Malo Mart", "Kakariko Village" }, + { 0x61, 0x01, 0x6101, "F_0806", "Talked to Hanch on the first day", "Ordon Village" }, + { 0x62, 0x80, 0x6280, "F_0807", "Talked to Colin on the first day", "Ordon Village" }, + { 0x62, 0x40, 0x6240, "F_0808", "Talked to Beth on the first day", "Ordon Village" }, + { 0x62, 0x20, 0x6220, "F_0809", "3 groupie girls (first conversation where they become Link's groupies)", "Castle Town" }, + { 0x62, 0x10, 0x6210, "F_0810", "Learned ghost's scent", "LV4 Dungeon" }, + { 0x62, 0x08, 0x6208, "F_0811", "Attempted Star Game for the first time", "Castle Town" }, + //{ 0x62, 0x04, 0x6204, "F_0812", "N/A", "N/A" }, + //{ 0x62, 0x02, 0x6202, "F_0813", "N/A", "N/A" }, + //{ 0x62, 0x01, 0x6201, "F_0814", "N/A", "N/A" }, + //{ 0x63, 0x80, 0x6380, "F_0815", "N/A", "N/A" }, + //{ 0x63, 0x40, 0x6340, "F_0816", "N/A", "N/A" }, + //{ 0x63, 0x20, 0x6320, "F_0817", "N/A", "N/A" }, + //{ 0x63, 0x10, 0x6310, "F_0818", "N/A", "N/A" }, + //{ 0x63, 0x08, 0x6308, "F_0819", "N/A", "N/A" }, + //{ 0x63, 0x04, 0x6304, "F_0820", "N/A", "N/A" }, + { 0x63, 0x02, 0x6302, "KORO2_ALLCLEAR", "After all stages (8-8) of roll goal game cleared", "Fishing" }, +}; + + +struct MultiBitEventFlag { + uint8_t byteInd; + const char* description; +}; + +inline const MultiBitEventFlag duskImguiU8Events[] = { + { 0xf2, "Ordon Catfish size record in Fishing Hole (in cm)" }, + { 0xf3, "Hylian Pike size record in Fishing Hole (in cm)" }, + { 0xf4, "Hylian Loach size record in Fishing Hole (in cm)" }, + { 0xf5, "Hyrule Bass size record in Fishing Hole (in cm)" }, + { 0xf6, "Number of Rollgoal Levels beaten" }, +}; + +inline const MultiBitEventFlag duskImguiU16Events[] = { + { 0xf7, "Rupees donated to Charlo" }, + { 0xf9, "Rupees donated towards the current Malo Mart donation goal" }, +}; + +inline const MultiBitEventFlag duskImguiSwappedU16Events[] = { + { 0xfb, "Rupees paid to Trill" }, + { 0xfd, "Rupees owed to Trill" }, +}; + +#endif // !DUSK_IMGUI_EVENTFLAGS_HPP \ No newline at end of file diff --git a/src/dusk/imgui/ImGuiFirstRunPreset.cpp b/src/dusk/imgui/ImGuiFirstRunPreset.cpp index c218185c92..fb2d181562 100644 --- a/src/dusk/imgui/ImGuiFirstRunPreset.cpp +++ b/src/dusk/imgui/ImGuiFirstRunPreset.cpp @@ -13,7 +13,7 @@ static void ApplyPresetClassic() { auto& s = getSettings(); s.video.lockAspectRatio.setValue(true); s.game.bloomMode.setValue(BloomMode::Classic); - VILockAspectRatio(defaultAspectRatioW, defaultAspectRatioH); + AuroraSetViewportPolicy(AURORA_VIEWPORT_FIT); } static void ApplyPresetHD() { diff --git a/src/dusk/imgui/ImGuiMenuEnhancements.cpp b/src/dusk/imgui/ImGuiMenuEnhancements.cpp deleted file mode 100644 index b7d75fea2f..0000000000 --- a/src/dusk/imgui/ImGuiMenuEnhancements.cpp +++ /dev/null @@ -1,202 +0,0 @@ -#include "imgui.h" - -#include "ImGuiMenuEnhancements.hpp" -#include "ImGuiConfig.hpp" -#include "dusk/settings.h" - -namespace dusk { - ImGuiMenuEnhancements::ImGuiMenuEnhancements() {} - - void ImGuiMenuEnhancements::draw() { - if (ImGui::BeginMenu("Enhancements")) { - if (ImGui::BeginMenu("Gameplay")) { - ImGui::SeparatorText("Preferences"); - - config::ImGuiCheckbox("Mirror Mode", getSettings().game.enableMirrorMode); - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("Mirrors the world horizontally, matching the Wii version of the game."); - } - - config::ImGuiCheckbox("Disable Main HUD", getSettings().game.disableMainHUD); - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("Disables the main HUD of the game.\n" - "Useful for recording or a more immersive experience!"); - } - - ImGui::SeparatorText("Difficulty"); - - config::ImGuiSliderInt("Damage Multiplier", getSettings().game.damageMultiplier, 1, 8, "x%d"); - - config::ImGuiCheckbox("Instant Death", getSettings().game.instantDeath); - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("Any hit will instantly kill you."); - } - - config::ImGuiCheckbox("No Heart Drops", getSettings().game.noHeartDrops); - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("Hearts will never drop from enemies,\n" - "pots and various other places."); - } - - ImGui::SeparatorText("Quality of Life"); - - config::ImGuiCheckbox("Bigger Wallets", getSettings().game.biggerWallets); - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("Wallet sizes are like in the HD version. (500, 1000, 2000)"); - } - - config::ImGuiCheckbox("Disable Rupee Cutscenes", getSettings().game.disableRupeeCutscenes); - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("Rupees won't play cutscenes after you've collected them the first time."); - } - - config::ImGuiCheckbox("Faster Climbing", getSettings().game.fastClimbing); - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("Quicker climbing on ladders and vines like the HD version."); - } - - config::ImGuiCheckbox("Faster Tears of Light", getSettings().game.fastTears); - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("Tears of Light dropped by Shadow Insects pop out faster like the HD version."); - } - - config::ImGuiCheckbox("Instant Saves", getSettings().game.instantSaves); - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("Skip the delay when writing to the Memory Card."); - } - - config::ImGuiCheckbox("No Climbing Miss Animation", getSettings().game.noMissClimbing); - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("Prevents Link from playing a struggle animation\n" - "when grabbing ledges or climbing on vines."); - } - - config::ImGuiCheckbox("No Rupee Returns", getSettings().game.noReturnRupees); - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("Always collect Rupees even if your Wallet is too full."); - } - - config::ImGuiCheckbox("No Sword Recoil", getSettings().game.noSwordRecoil); - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("Link won't recoil when his sword hits walls."); - } - - config::ImGuiCheckbox("Skip TV Settings Screen", getSettings().game.hideTvSettingsScreen); - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("Skip the TV calibration screen shown when loading a save."); - } - - config::ImGuiCheckbox("Skip Warning Screen", getSettings().game.skipWarningScreen); - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("Skip the warning screen shown when starting the game."); - } - - config::ImGuiCheckbox("Sun's Song (R+X)", getSettings().game.sunsSong); - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("Allows Wolf Link to howl and change the time of day."); - } - - config::ImGuiCheckbox("Quick Transform (R+Y)", getSettings().game.enableQuickTransform); - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("Transform instantly by pressing R and Y simultaneously."); - } - - ImGui::EndMenu(); - } - - if (ImGui::BeginMenu("Graphics")) { - config::ImGuiSliderInt("Shadow Resolution", getSettings().game.shadowResolutionMultiplier, 1, 8, "x%d"); - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("Improves the shadow resolution, making them higher quality."); - } - - config::ImGuiCheckbox("Unlock Framerate", getSettings().game.enableFrameInterpolation); - const bool frameInterpolationHovered = ImGui::IsItemHovered(); - ImGui::SameLine(); - ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 0.72f, 0.2f, 1.0f)); - ImGui::TextUnformatted("[EXPERIMENTAL]"); - ImGui::PopStyleColor(); - if (frameInterpolationHovered || ImGui::IsItemHovered()) { - ImGui::SetTooltip("Uses inter-frame interpolation to enable higher frame rates.\nVisual artifacts, animation glitches, or instability may occur."); - } - - ImGui::EndMenu(); - } - - if (ImGui::BeginMenu("Audio")) { - config::ImGuiCheckbox("No Low HP Sound", getSettings().game.noLowHpSound); - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("Disable the beeping sound when having low health."); - } - - config::ImGuiCheckbox("Non-Stop Midna's Lament", getSettings().game.midnasLamentNonStop); - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("Prevents enemy music while Midna's Lament is playing."); - } - - ImGui::EndMenu(); - } - - if (ImGui::BeginMenu("Input")) { - config::ImGuiCheckbox("Invert Camera X Axis", getSettings().game.invertCameraXAxis); - - ImGui::SeparatorText("Gyro"); - - config::ImGuiCheckbox("Gyro Aim", getSettings().game.enableGyroAim); - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("Enables the gyroscope on supported controllers while aiming the\n" - "Slingshot, Gale Boomerang, Hero's Bow, Clawshot(s), Ball and Chain, and Dominion Rod."); - } - - config::ImGuiSliderFloat("Gyro Pitch Sensitivity", getSettings().game.gyroAimSensitivityY, 0.25f, 4.0f, "%.2f"); - config::ImGuiSliderFloat("Gyro Yaw Sensitivity", getSettings().game.gyroAimSensitivityX, 0.25f, 4.0f, "%.2f"); - config::ImGuiCheckbox("Invert Gyro Pitch", getSettings().game.gyroAimInvertPitch); - config::ImGuiCheckbox("Invert Gyro Yaw", getSettings().game.gyroAimInvertYaw); - - ImGui::SeparatorText("Tools"); - - config::ImGuiCheckbox("Turbo Key", getSettings().game.enableTurboKeybind); - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("Hold TAB to increase game speed by up to 4x."); - } - - ImGui::EndMenu(); - } - - ImGui::Separator(); - - if (ImGui::BeginMenu("Cheats")) { - config::ImGuiCheckbox("Fast Iron Boots", getSettings().game.enableFastIronBoots); - - config::ImGuiCheckbox("Can Transform Anywhere", getSettings().game.canTransformAnywhere); - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("Allows you to transform even if NPCs are looking."); - } - - config::ImGuiCheckbox("Fast Spinner", getSettings().game.fastSpinner); - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("Speeds up Spinner movement when holding R."); - } - - config::ImGuiCheckbox("Free Magic Armor", getSettings().game.freeMagicArmor); - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("Makes the magic armor work without rupees."); - } - - ImGui::EndMenu(); - } - - if (ImGui::BeginMenu("Technical")) { - config::ImGuiCheckbox("Restore Wii 1.0 Glitches", getSettings().game.restoreWiiGlitches); - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("Restores patched glitches from Wii USA 1.0,\n" - "the first released version."); - } - - ImGui::EndMenu(); - } - - ImGui::EndMenu(); - } - } -} diff --git a/src/dusk/imgui/ImGuiMenuEnhancements.hpp b/src/dusk/imgui/ImGuiMenuEnhancements.hpp deleted file mode 100644 index f40baaad65..0000000000 --- a/src/dusk/imgui/ImGuiMenuEnhancements.hpp +++ /dev/null @@ -1,18 +0,0 @@ -#ifndef DUSK_IMGUI_MENUENHANCEMENTS_HPP -#define DUSK_IMGUI_MENUENHANCEMENTS_HPP - -#include -#include -#include - -#include "imgui.h" - -namespace dusk { - class ImGuiMenuEnhancements { - public: - ImGuiMenuEnhancements(); - void draw(); - }; -} - -#endif // DUSK_IMGUI_MENUENHANCEMENTS_HPP diff --git a/src/dusk/imgui/ImGuiMenuGame.cpp b/src/dusk/imgui/ImGuiMenuGame.cpp index 48c64f8f0e..448bd531ff 100644 --- a/src/dusk/imgui/ImGuiMenuGame.cpp +++ b/src/dusk/imgui/ImGuiMenuGame.cpp @@ -1,15 +1,15 @@ #include "fmt/format.h" #include "imgui.h" +#include "ImGuiEngine.hpp" #include "ImGuiConsole.hpp" #include "ImGuiMenuGame.hpp" #include "ImGuiConfig.hpp" -#include #include "JSystem/JUtility/JUTGamePad.h" #include "dusk/audio/DuskAudioSystem.h" #include "dusk/audio/DuskDsp.hpp" -#include "dusk/dusk.h" +#include "dusk/main.h" #include "dusk/hotkeys.h" #include "dusk/settings.h" #include "m_Do/m_Do_controller_pad.h" @@ -18,7 +18,13 @@ #include #include -#include "dusk/main.h" +namespace { +constexpr int kInternalResolutionScaleMax = 12; +} // namespace + +namespace aurora::gx { +extern bool enableLodBias; +} namespace dusk { void ImGuiMenuGame::ToggleFullscreen() { @@ -30,116 +36,13 @@ namespace dusk { ImGuiMenuGame::ImGuiMenuGame() {} void ImGuiMenuGame::draw() { - if (ImGui::BeginMenu("Game")) { - if (ImGui::BeginMenu("Graphics")) { - if (ImGui::MenuItem("Toggle Fullscreen", hotkeys::TOGGLE_FULLSCREEN)) { - ToggleFullscreen(); - } - - if (ImGui::MenuItem("Default Window Size")) { - getSettings().video.enableFullscreen.setValue(false); - VISetWindowFullscreen(false); - VISetWindowSize(FB_WIDTH * 2, FB_HEIGHT * 2); - VICenterWindow(); - } - - bool vsync = getSettings().video.enableVsync; - if (ImGui::Checkbox("Enable Vsync", &vsync)) { - getSettings().video.enableVsync.setValue(vsync); - aurora_enable_vsync(vsync); - config::Save(); - } - - bool lockAspect = getSettings().video.lockAspectRatio; - if (ImGui::Checkbox("Force 4:3 Aspect Ratio", &lockAspect)) { - getSettings().video.lockAspectRatio.setValue(lockAspect); - - if (lockAspect) { - VILockAspectRatio(defaultAspectRatioW, defaultAspectRatioH); - } else { - VIUnlockAspectRatio(); - } - - config::Save(); - } - - constexpr const char* bloomModeNames[] = {"Off", "Classic", "Dusk"}; - int bloomMode = static_cast(getSettings().game.bloomMode.getValue()); - if (ImGui::BeginCombo("Bloom", bloomModeNames[bloomMode])) { - for (int i = 0; i < IM_ARRAYSIZE(bloomModeNames); i++) { - const bool selected = bloomMode == i; - if (ImGui::Selectable(bloomModeNames[i], selected)) { - getSettings().game.bloomMode.setValue(static_cast(i)); - config::Save(); - } - if (selected) { - ImGui::SetItemDefaultFocus(); - } - } - ImGui::EndCombo(); - } - - bool bloomOff = bloomMode == static_cast(BloomMode::Off); - if (bloomOff) ImGui::BeginDisabled(); - float mult = getSettings().game.bloomMultiplier.getValue(); - if (ImGui::SliderFloat("Bloom Brightness", &mult, 0.0f, 1.0f, "%.2f")) { - getSettings().game.bloomMultiplier.setValue(mult); - config::Save(); - } - if (bloomOff) ImGui::EndDisabled(); - - config::ImGuiCheckbox("Enable Water Refraction", getSettings().game.enableWaterRefraction); - - ImGui::EndMenu(); - } - - if (ImGui::BeginMenu("Audio")) { - ImGui::Text("Master Volume"); - if (config::ImGuiSliderInt("##masterVolume", getSettings().audio.masterVolume, 0, 100)) { - dusk::audio::SetMasterVolume(getSettings().audio.masterVolume / 100.0f); - } - - if (config::ImGuiCheckbox("Enable Reverb", getSettings().audio.enableReverb)) { - dusk::audio::SetEnableReverb(getSettings().audio.enableReverb); - } - /* - // TODO: Implement additional settings - ImGui::Text("Main Music Volume"); - ImGui::SliderFloat("##mainMusicVolume", &getSettings().audio.mainMusicVolume, 0, 100); - - ImGui::Text("Sub Music Volume"); - ImGui::SliderFloat("##subMusicVolume", &getSettings().audio.subMusicVolume, 0, 100); - - ImGui::Text("Sound Effects Volume"); - ImGui::SliderFloat("##soundEffectsVolume", &getSettings().audio.soundEffectsVolume, 0, 100); - - ImGui::Text("Fanfare Volume"); - ImGui::SliderFloat("##fanfareVolume", &getSettings().audio.fanfareVolume, 0, 100); - - Z2AudioMgr* audioMgr = Z2AudioMgr::getInterface(); - if (audioMgr != nullptr) { - } - */ - - ImGui::EndMenu(); - } - - if (ImGui::BeginMenu("Controller")) { - ImGui::MenuItem("Configure Controller", nullptr, &m_showControllerConfig); - ImGui::Checkbox("Show Input Viewer", &m_showInputViewer); - - ImGui::EndMenu(); - } - - if (ImGui::BeginMenu("Interface")) { - config::ImGuiCheckbox("Skip Pre-Launch UI", getSettings().backend.skipPreLaunchUI); - config::ImGuiCheckbox("Show Pipeline Compilation", getSettings().backend.showPipelineCompilation); -#if DUSK_ENABLE_SENTRY_NATIVE - config::ImGuiCheckbox("Enable Crash Reporting", getSettings().backend.enableCrashReporting); -#endif - - ImGui::EndMenu(); - } + if (ImGui::BeginMenu("Settings")) { + drawAudioMenu(); + drawCheatsMenu(); + drawGameplayMenu(); + drawGraphicsMenu(); + drawInputMenu(); + drawInterfaceMenu(); ImGui::Separator(); @@ -147,7 +50,7 @@ namespace dusk { JUTGamePad::C3ButtonReset::sResetSwitchPushing = true; } - if (ImGui::MenuItem("Exit")) { + if (!IsMobile && ImGui::MenuItem("Exit")) { dusk::IsRunning = false; } @@ -155,6 +58,398 @@ namespace dusk { } } + void ImGuiMenuGame::drawGraphicsMenu() { + if (ImGui::BeginMenu("Graphics")) { + ImGui::SeparatorText("Display"); + + if (!IsMobile) { + if (ImGui::MenuItem("Toggle Fullscreen", hotkeys::TOGGLE_FULLSCREEN)) { + ToggleFullscreen(); + } + + if (ImGui::Button("Restore Default Window Size")) { + getSettings().video.enableFullscreen.setValue(false); + VISetWindowFullscreen(false); + VISetWindowSize(FB_WIDTH * 2, FB_HEIGHT * 2); + VICenterWindow(); + } + } + + ImGui::Separator(); + + bool vsync = getSettings().video.enableVsync; + if (ImGui::Checkbox("Enable VSync", &vsync)) { + getSettings().video.enableVsync.setValue(vsync); + aurora_enable_vsync(vsync); + config::Save(); + } + + bool lockAspect = getSettings().video.lockAspectRatio; + if (ImGui::Checkbox("Force 4:3 Aspect Ratio", &lockAspect)) { + getSettings().video.lockAspectRatio.setValue(lockAspect); + + if (lockAspect) { + AuroraSetViewportPolicy(AURORA_VIEWPORT_FIT); + } else { + AuroraSetViewportPolicy(AURORA_VIEWPORT_STRETCH); + } + + config::Save(); + } + + ImGui::SeparatorText("Resolution"); + + u32 internalResolutionWidth = 0; + u32 internalResolutionHeight = 0; + AuroraGetRenderSize(&internalResolutionWidth, &internalResolutionHeight); + ImGui::TextDisabled("Current internal resolution: %ux%u", internalResolutionWidth, + internalResolutionHeight); + + int scale = std::clamp(getSettings().game.internalResolutionScale.getValue(), 0, + kInternalResolutionScaleMax); + if (ImGui::SliderInt("Internal Resolution", &scale, 0, kInternalResolutionScaleMax, + scale == 0 ? "Auto" : "%dx")) + { + getSettings().game.internalResolutionScale.setValue(scale); + VISetFrameBufferScale(static_cast(scale)); + config::Save(); + } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Auto renders at the native window resolution.\n" + "Higher values scale the game's internal framebuffer."); + } + + config::ImGuiSliderInt("Shadow Resolution", getSettings().game.shadowResolutionMultiplier, 1, 8, "x%d"); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Improves the shadow resolution, making them higher quality."); + } + + ImGui::SeparatorText("Post-Processing"); + + constexpr const char* bloomModeNames[] = {"Off", "Classic", "Dusk"}; + int bloomMode = static_cast(getSettings().game.bloomMode.getValue()); + if (ImGui::BeginCombo("Bloom", bloomModeNames[bloomMode])) { + for (int i = 0; i < IM_ARRAYSIZE(bloomModeNames); i++) { + const bool selected = bloomMode == i; + if (ImGui::Selectable(bloomModeNames[i], selected)) { + getSettings().game.bloomMode.setValue(static_cast(i)); + config::Save(); + } + if (selected) { + ImGui::SetItemDefaultFocus(); + } + } + ImGui::EndCombo(); + } + + bool bloomOff = bloomMode == static_cast(BloomMode::Off); + if (bloomOff) ImGui::BeginDisabled(); + float mult = getSettings().game.bloomMultiplier.getValue(); + if (ImGui::SliderFloat("Bloom Brightness", &mult, 0.0f, 1.0f, "%.2f")) { + getSettings().game.bloomMultiplier.setValue(mult); + config::Save(); + } + if (bloomOff) ImGui::EndDisabled(); + + ImGui::SeparatorText("Rendering"); + + config::ImGuiCheckbox("Unlock Framerate", getSettings().game.enableFrameInterpolation); + const bool frameInterpolationHovered = ImGui::IsItemHovered(); + ImGui::SameLine(); + ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 0.72f, 0.2f, 1.0f)); + ImGui::TextUnformatted("[EXPERIMENTAL]"); + ImGui::PopStyleColor(); + if (frameInterpolationHovered || ImGui::IsItemHovered()) { + ImGui::SetTooltip("Uses inter-frame interpolation to enable higher frame rates.\nVisual artifacts, animation glitches, or instability may occur."); + } + + ImGui::Checkbox("Enable LOD Bias", &aurora::gx::enableLodBias); + + config::ImGuiCheckbox("Enable Depth of Field", getSettings().game.enableDepthOfField); + + ImGui::EndMenu(); + } + } + + void ImGuiMenuGame::drawGameplayMenu() { + if (ImGui::BeginMenu("Gameplay")) { + ImGui::SeparatorText("General"); + + config::ImGuiCheckbox("Mirror Mode", getSettings().game.enableMirrorMode); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Mirrors the world horizontally, matching the Wii version of the game."); + } + + config::ImGuiCheckbox("Disable Main HUD", getSettings().game.disableMainHUD); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Disables the main HUD of the game.\n" + "Useful for recording or a more immersive experience!"); + } + + config::ImGuiCheckbox("Restore Wii 1.0 Glitches", getSettings().game.restoreWiiGlitches); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Restores patched glitches from Wii USA 1.0,\n" + "the first released version."); + } + + ImGui::SeparatorText("Difficulty"); + + config::ImGuiSliderInt("Damage Multiplier", getSettings().game.damageMultiplier, 1, 8, "x%d"); + + config::ImGuiCheckbox("Instant Death", getSettings().game.instantDeath); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Any hit will instantly kill you."); + } + + config::ImGuiCheckbox("No Heart Drops", getSettings().game.noHeartDrops); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Hearts will never drop from enemies,\n" + "pots and various other places."); + } + + ImGui::SeparatorText("Quality of Life"); + + config::ImGuiCheckbox("Bigger Wallets", getSettings().game.biggerWallets); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Wallet sizes are like in the HD version. (500, 1000, 2000)"); + } + + config::ImGuiCheckbox("Disable Rupee Cutscenes", getSettings().game.disableRupeeCutscenes); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Rupees won't play cutscenes after you've collected them the first time."); + } + + config::ImGuiCheckbox("Faster Climbing", getSettings().game.fastClimbing); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Quicker climbing on ladders and vines like the HD version."); + } + + config::ImGuiCheckbox("Faster Tears of Light", getSettings().game.fastTears); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Tears of Light dropped by Shadow Insects pop out faster like the HD version."); + } + + config::ImGuiCheckbox("Instant Saves", getSettings().game.instantSaves); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Skip the delay when writing to the Memory Card."); + } + + config::ImGuiCheckbox("Hold B for Instant Text", getSettings().game.instantText); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Make text scroll immediately by holding B."); + } + + config::ImGuiCheckbox("No Climbing Miss Animation", getSettings().game.noMissClimbing); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Prevents Link from playing a struggle animation\n" + "when grabbing ledges or climbing on vines."); + } + + config::ImGuiCheckbox("No Rupee Returns", getSettings().game.noReturnRupees); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Always collect Rupees even if your Wallet is too full."); + } + + config::ImGuiCheckbox("No Sword Recoil", getSettings().game.noSwordRecoil); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Link won't recoil when his sword hits walls."); + } + + config::ImGuiCheckbox("Skip TV Settings Screen", getSettings().game.hideTvSettingsScreen); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Skip the TV calibration screen shown when loading a save."); + } + + config::ImGuiCheckbox("Skip Warning Screen", getSettings().game.skipWarningScreen); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Skip the warning screen shown when starting the game."); + } + + config::ImGuiCheckbox("Sun's Song (R+X)", getSettings().game.sunsSong); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Allows Wolf Link to howl and change the time of day."); + } + + config::ImGuiCheckbox("Quick Transform (R+Y)", getSettings().game.enableQuickTransform); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Transform instantly by pressing R and Y simultaneously."); + } + + ImGui::EndMenu(); + } + } + + void ImGuiMenuGame::drawCheatsMenu() { + if (ImGui::BeginMenu("Cheats")) { + ImGui::SeparatorText("Resources"); + config::ImGuiCheckbox("Infinite Hearts", getSettings().game.infiniteHearts); + config::ImGuiCheckbox("Infinite Arrows", getSettings().game.infiniteArrows); + config::ImGuiCheckbox("Infinite Bombs", getSettings().game.infiniteBombs); + config::ImGuiCheckbox("Infinite Oil", getSettings().game.infiniteOil); + config::ImGuiCheckbox("Infinite Oxygen", getSettings().game.infiniteOxygen); + config::ImGuiCheckbox("Infinite Rupees", getSettings().game.infiniteRupees); + + ImGui::SeparatorText("Abilities"); + config::ImGuiCheckbox("Moon Jump (R+A)", getSettings().game.moonJump); + config::ImGuiCheckbox("Super Clawshot", getSettings().game.superClawshot); + config::ImGuiCheckbox("Always Greatspin", getSettings().game.alwaysGreatspin); + config::ImGuiCheckbox("Fast Iron Boots", getSettings().game.enableFastIronBoots); + + config::ImGuiCheckbox("Can Transform Anywhere", getSettings().game.canTransformAnywhere); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Allows you to transform even if NPCs are looking."); + } + + config::ImGuiCheckbox("Fast Spinner", getSettings().game.fastSpinner); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Speeds up Spinner movement when holding R."); + } + + config::ImGuiCheckbox("Free Magic Armor", getSettings().game.freeMagicArmor); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Makes the magic armor work without rupees."); + } + + ImGui::EndMenu(); + } + } + + void ImGuiMenuGame::drawAudioMenu() { + if (ImGui::BeginMenu("Audio")) { + + ImGui::SeparatorText("Volume"); + + ImGui::Text("Master Volume"); + if (config::ImGuiSliderInt("##masterVolume", getSettings().audio.masterVolume, 0, 100)) { + dusk::audio::SetMasterVolume(getSettings().audio.masterVolume / 100.0f); + } + + /* + // TODO: Implement additional settings + ImGui::Text("Main Music Volume"); + ImGui::SliderFloat("##mainMusicVolume", &getSettings().audio.mainMusicVolume, 0, 100); + + ImGui::Text("Sub Music Volume"); + ImGui::SliderFloat("##subMusicVolume", &getSettings().audio.subMusicVolume, 0, 100); + + ImGui::Text("Sound Effects Volume"); + ImGui::SliderFloat("##soundEffectsVolume", &getSettings().audio.soundEffectsVolume, 0, 100); + + ImGui::Text("Fanfare Volume"); + ImGui::SliderFloat("##fanfareVolume", &getSettings().audio.fanfareVolume, 0, 100); + + Z2AudioMgr* audioMgr = Z2AudioMgr::getInterface(); + if (audioMgr != nullptr) { + } + */ + + ImGui::SeparatorText("Effects"); + + if (config::ImGuiCheckbox("Enable Reverb", getSettings().audio.enableReverb)) { + dusk::audio::SetEnableReverb(getSettings().audio.enableReverb); + } + + + ImGui::SeparatorText("Tweaks"); + + config::ImGuiCheckbox("No Low HP Sound", getSettings().game.noLowHpSound); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Disable the beeping sound when having low health."); + } + + config::ImGuiCheckbox("Non-Stop Midna's Lament", getSettings().game.midnasLamentNonStop); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Prevents enemy music while Midna's Lament is playing."); + } + + ImGui::EndMenu(); + } + } + + void ImGuiMenuGame::drawInputMenu() { + if (ImGui::BeginMenu("Input")) { + ImGui::SeparatorText("Controller"); + + if (ImGui::Button("Configure Controller")){ + m_showControllerConfig = !m_showControllerConfig; + } + + ImGui::SeparatorText("Camera"); + + config::ImGuiCheckbox("Invert Camera X Axis", getSettings().game.invertCameraXAxis); + + ImGui::SeparatorText("Gyro"); + + config::ImGuiCheckbox("Gyro Aim", getSettings().game.enableGyroAim); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Enables the gyroscope on supported controllers\n" + "while in look mode (C-Up) and while aiming the\n" + "Slingshot, Gale Boomerang, Hero's Bow, Clawshot(s),\n" + "Ball and Chain, and Dominion Rod."); + } + + config::ImGuiCheckbox("Gyro Rollgoal", getSettings().game.enableGyroRollgoal); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Enables the gyroscope on supported controllers to\n" + "tilt the Rollgoal table in Hena's Cabin."); + } + + if (getSettings().game.enableGyroAim || getSettings().game.enableGyroRollgoal) { + config::ImGuiSliderFloat("Gyro Pitch Sensitivity", getSettings().game.gyroSensitivityY, 0.25f, 4.0f, "%.2f"); + config::ImGuiSliderFloat("Gyro Yaw Sensitivity", getSettings().game.gyroSensitivityX, 0.25f, 4.0f, "%.2f"); + + if (getSettings().game.enableGyroRollgoal) { + config::ImGuiSliderFloat("Rollgoal Sensitivity", getSettings().game.gyroSensitivityRollgoal, 0.25f, 4.0f, "%.2f"); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Additional multiplier for scaling how strongly\n" + "the gyroscope affects the Rollgoal table."); + } + } + + config::ImGuiSliderFloat("Gyro Deadband", getSettings().game.gyroDeadband, 0.0f, 0.5f, "%.3f"); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Angular rates below this magnitude are treated as zero,\n" + "reducing drift and jitter when the controller is still."); + } + + config::ImGuiSliderFloat("Gyro Smoothing", getSettings().game.gyroSmoothing, 0.0f, 1.0f, "%.2f"); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Low values track raw gyro input more closely,\n" + "while higher values smooth out input over time."); + } + + config::ImGuiCheckbox("Invert Gyro Pitch", getSettings().game.gyroInvertPitch); + config::ImGuiCheckbox("Invert Gyro Yaw", getSettings().game.gyroInvertYaw); + } + + ImGui::SeparatorText("Tools"); + + config::ImGuiCheckbox("Turbo Key", getSettings().game.enableTurboKeybind); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Hold TAB to increase game speed by up to 4x."); + } + + ImGui::Checkbox("Show Input Viewer", &m_showInputViewer); + + ImGui::EndMenu(); + } + } + + void ImGuiMenuGame::drawInterfaceMenu() { + if (ImGui::BeginMenu("Interface")) { + config::ImGuiCheckbox("Skip Pre-Launch UI", getSettings().backend.skipPreLaunchUI); + config::ImGuiCheckbox("Show Pipeline Compilation", getSettings().backend.showPipelineCompilation); +#if DUSK_ENABLE_SENTRY_NATIVE + config::ImGuiCheckbox("Enable Crash Reporting", getSettings().backend.enableCrashReporting); +#endif + if (!IsMobile) { + config::ImGuiCheckbox("Pause on Focus Lost", getSettings().game.pauseOnFocusLost); + } + + ImGui::EndMenu(); + } + } + static void drawVirtualStick(const char* id, const ImVec2& stick) { float scale = ImGuiScale(); ImGui::SetCursorPos(ImVec2(ImGui::GetCursorPos().x + 45 * scale, ImGui::GetCursorPos().y + 10)); @@ -304,6 +599,7 @@ namespace dusk { m_controllerConfig.m_pendingButtonMapping = nullptr; m_controllerConfig.m_pendingPort = -1; PADBlockInput(false); + PADSerializeMappings(); } } @@ -316,6 +612,7 @@ namespace dusk { m_controllerConfig.m_pendingAxisMapping = nullptr; m_controllerConfig.m_pendingPort = -1; PADBlockInput(false); + PADSerializeMappings(); } else { auto nativeButton = PADGetNativeButtonPressed(m_controllerConfig.m_pendingPort); if (nativeButton != -1) { @@ -324,6 +621,7 @@ namespace dusk { m_controllerConfig.m_pendingAxisMapping = nullptr; m_controllerConfig.m_pendingPort = -1; PADBlockInput(false); + PADSerializeMappings(); } } } @@ -400,11 +698,6 @@ namespace dusk { // if "None" selected PADClearPort(m_controllerConfig.m_selectedPort); } - } - - // save mappings button - ImGui::SameLine(); - if (ImGui::Button("Save")) { PADSerializeMappings(); } @@ -412,6 +705,7 @@ namespace dusk { ImGui::SameLine(); if (ImGui::Button("Default")) { PADRestoreDefaultMapping(m_controllerConfig.m_selectedPort); + PADSerializeMappings(); } // buttons panel @@ -508,6 +802,7 @@ namespace dusk { float tmp = static_cast(deadZones->leftTriggerActivationZone * 100.f) / 32767.f; if (ImGui::DragFloat("##LThreshold", &tmp, 0.5f, 0.f, 100.f, "%.3f%%")) { deadZones->leftTriggerActivationZone = static_cast((tmp / 100.f) * 32767); + PADSerializeMappings(); } } } @@ -519,6 +814,7 @@ namespace dusk { float tmp = static_cast(deadZones->rightTriggerActivationZone * 100.f) / 32767.f; if (ImGui::DragFloat("##RThreshold", &tmp, 0.5f, 0.f, 100.f, "%.3f%%")) { deadZones->rightTriggerActivationZone = static_cast((tmp / 100.f) * 32767); + PADSerializeMappings(); } } } @@ -581,6 +877,7 @@ namespace dusk { float tmp = static_cast(deadZones->stickDeadZone * 100.f) / 32767.f; if (ImGui::DragFloat("##mainDeadZone", &tmp, 0.5f, 0.f, 100.f, "%.3f%%")) { deadZones->stickDeadZone = static_cast((tmp / 100.f) * 32767); + PADSerializeMappings(); } } } @@ -643,6 +940,7 @@ namespace dusk { float tmp = static_cast(deadZones->substickDeadZone * 100.f) / 32767.f; if (ImGui::DragFloat("##subDeadZone", &tmp, 0.5f, 0.f, 100.f, "%.3f%%")) { deadZones->substickDeadZone = static_cast((tmp / 100.f) * 32767); + PADSerializeMappings(); } } } @@ -654,8 +952,12 @@ namespace dusk { ImGuiBeginGroupPanel("Options", ImVec2(150 * scale, 20 * scale)); if (deadZones != nullptr) { - ImGui::Checkbox("Enable Dead Zones", &deadZones->useDeadzones); - ImGui::Checkbox("Emulate Triggers", &deadZones->emulateTriggers); + if (ImGui::Checkbox("Enable Dead Zones", &deadZones->useDeadzones)) { + PADSerializeMappings(); + } + if (ImGui::Checkbox("Emulate Triggers", &deadZones->emulateTriggers)) { + PADSerializeMappings(); + } } ImGuiEndGroupPanel(); diff --git a/src/dusk/imgui/ImGuiMenuGame.hpp b/src/dusk/imgui/ImGuiMenuGame.hpp index 77e5952d1a..e21374c8f4 100644 --- a/src/dusk/imgui/ImGuiMenuGame.hpp +++ b/src/dusk/imgui/ImGuiMenuGame.hpp @@ -19,6 +19,13 @@ namespace dusk { static void ToggleFullscreen(); private: + void drawAudioMenu(); + void drawInputMenu(); + void drawGraphicsMenu(); + void drawGameplayMenu(); + void drawCheatsMenu(); + void drawInterfaceMenu(); + struct { int m_selectedPort = 0; bool m_isReading = false; @@ -30,6 +37,7 @@ namespace dusk { bool m_showControllerConfig = false; bool m_showInputViewer = false; + bool m_showInputViewerGyro = false; int m_inputOverlayCorner = 3; std::string m_controllerName; }; diff --git a/src/dusk/imgui/ImGuiMenuTools.cpp b/src/dusk/imgui/ImGuiMenuTools.cpp index 7b317c978b..97f5b5a8d3 100644 --- a/src/dusk/imgui/ImGuiMenuTools.cpp +++ b/src/dusk/imgui/ImGuiMenuTools.cpp @@ -2,6 +2,7 @@ #include "imgui.h" #include "aurora/gfx.h" +#include "ImGuiConfig.hpp" #include "dusk/hotkeys.h" #include "dusk/settings.h" #include "ImGuiConsole.hpp" @@ -15,10 +16,58 @@ #include "dusk/main.h" #include "m_Do/m_Do_main.h" +#include +#include + +#if defined(__APPLE__) +#include +#endif + +#if defined(_WIN32) || (defined(__APPLE__) && !TARGET_OS_IOS && !TARGET_OS_MACCATALYST) || (defined(__linux__) && !defined(__ANDROID__)) +#define DUSK_CAN_OPEN_DATA_FOLDER 1 + +namespace fs = std::filesystem; + +static void OpenDataFolder() { + const std::string path = fs::absolute(dusk::ConfigPath).generic_string(); +#if defined(_WIN32) + const std::string url = std::string("file:///") + path; +#else + const std::string url = std::string("file://") + path; +#endif + (void)SDL_OpenURL(url.c_str()); +} +#else +#define DUSK_CAN_OPEN_DATA_FOLDER 0 +#endif + namespace dusk { ImGuiMenuTools::ImGuiMenuTools() {} void ImGuiMenuTools::draw() { + if (ImGui::BeginMenu("Tools")) { + if (!dusk::IsGameLaunched) { + ImGui::BeginDisabled(); + } + + ImGui::MenuItem("Save Editor", hotkeys::SHOW_SAVE_EDITOR, &m_showSaveEditor); + ImGui::MenuItem("Map Loader", hotkeys::SHOW_MAP_LOADER, &m_showMapLoader); + ImGui::MenuItem("State Share", hotkeys::SHOW_STATE_SHARE, &m_showStateShare); + + if (!dusk::IsGameLaunched) { + ImGui::EndDisabled(); + } + +#if DUSK_CAN_OPEN_DATA_FOLDER + ImGui::Separator(); + if (ImGui::MenuItem("Open Data Folder")) { + OpenDataFolder(); + } +#endif + + ImGui::EndMenu(); + } + if (ImGui::BeginMenu("Debug")) { bool developmentMode = mDoMain::developmentMode == 1; if (ImGui::Checkbox("Development Mode", &developmentMode)) { @@ -28,6 +77,15 @@ namespace dusk { ImGui::Separator(); auto& collisionView = getTransientSettings().collisionView; + if (ImGui::BeginMenu("Graphics Settings")) { + bool disableWaterRefraction = getSettings().game.disableWaterRefraction; + if (ImGui::Checkbox("Disable Water Refraction", &disableWaterRefraction)) { + getSettings().game.disableWaterRefraction.setValue(disableWaterRefraction); + config::Save(); + } + ImGui::EndMenu(); + } + if (ImGui::BeginMenu("Collision View")) { ImGui::Checkbox("Enable Terrain view", &collisionView.enableTerrainView); ImGui::Checkbox("Enable wireframe view", &collisionView.enableWireframe); @@ -49,12 +107,9 @@ namespace dusk { ImGui::MenuItem("Debug Overlay", hotkeys::SHOW_DEBUG_OVERLAY, &m_showDebugOverlay); ImGui::MenuItem("Heap Viewer", hotkeys::SHOW_HEAP_VIEWER, &m_showHeapOverlay); ImGui::MenuItem("Player Info", hotkeys::SHOW_PLAYER_INFO, &m_showPlayerInfo); - ImGui::MenuItem("Save Editor", hotkeys::SHOW_SAVE_EDITOR, &m_showSaveEditor); - ImGui::MenuItem("Map Loader", hotkeys::SHOW_MAP_LOADER, &m_showMapLoader); - ImGui::MenuItem("State Share", hotkeys::SHOW_STATE_SHARE, &m_showStateShare); ImGui::MenuItem("Debug Camera", hotkeys::SHOW_DEBUG_CAMERA, &m_showCameraOverlay); ImGui::MenuItem("Audio Debug", hotkeys::SHOW_AUDIO_DEBUG, &m_showAudioDebug); - + ImGui::MenuItem("Bloom", nullptr, &m_showBloomWindow); ImGui::MenuItem("Stub Log", nullptr, &m_showStubLog); if (!dusk::IsGameLaunched) { @@ -86,7 +141,9 @@ namespace dusk { ImGui::SetNextWindowBgAlpha(0.65f); if (ImGui::Begin("Debug Overlay", nullptr, windowFlags)) { ImGuiStringViewText(fmt::format(FMT_STRING("FPS: {:.2f}\n"), io.Framerate)); - ImGuiStringViewText(fmt::format(FMT_STRING("Frame usage: {:.1f}%\n"), frameUsagePct)); + if (frameUsagePct > 0.f) { + ImGuiStringViewText(fmt::format(FMT_STRING("Frame usage: {:.1f}%\n"), frameUsagePct)); + } ImGui::Separator(); diff --git a/src/dusk/imgui/ImGuiMenuTools.hpp b/src/dusk/imgui/ImGuiMenuTools.hpp index 05bdd9364b..e48aab2742 100644 --- a/src/dusk/imgui/ImGuiMenuTools.hpp +++ b/src/dusk/imgui/ImGuiMenuTools.hpp @@ -21,6 +21,7 @@ namespace dusk { void ShowHeapOverlay(); void ShowStubLog(); void ShowMapLoader(); + void ShowBloomWindow(); void ShowPlayerInfo(); void ShowAudioDebug(); void ShowSaveEditor(); @@ -41,6 +42,8 @@ namespace dusk { bool m_showMapLoader = false; + bool m_showBloomWindow = false; + bool m_showAudioDebug = false; struct { int mapIdx = -1; diff --git a/src/dusk/imgui/ImGuiPreLaunchWindow.cpp b/src/dusk/imgui/ImGuiPreLaunchWindow.cpp index c24dbd8247..ecbd41e158 100644 --- a/src/dusk/imgui/ImGuiPreLaunchWindow.cpp +++ b/src/dusk/imgui/ImGuiPreLaunchWindow.cpp @@ -4,13 +4,13 @@ #include "ImGuiEngine.hpp" #include "ImGuiPreLaunchWindow.hpp" +#include "../file_select.hpp" +#include "../iso_validate.hpp" #include "ImGuiConsole.hpp" #include "dusk/main.h" #include "dusk/settings.h" -#include "../iso_validate.hpp" #include -#include #include #include "aurora/lib/internal.hpp" @@ -46,30 +46,22 @@ static std::string ShowIsoInvalidError(const iso::ValidationError code) { } } -void fileDialogCallback(void* userdata, const char* const* filelist, [[maybe_unused]] int filter) { +void fileDialogCallback(void* userdata, const char* path, const char* error) { auto* self = static_cast(userdata); - self->m_errorString.clear(); - if (filelist != nullptr) { - if (filelist[0] == nullptr) { - // Cancelled - self->m_selectedIsoPath.clear(); - } else { - const auto path = filelist[0]; - const auto ret = iso::validate(path); - if (ret != iso::ValidationError::Success) { - self->m_selectedIsoPath.clear(); - self->m_errorString = std::move(ShowIsoInvalidError(ret)); - return; - } - self->m_selectedIsoPath = path; - getSettings().backend.isoPath.setValue(path); - config::Save(); - } - } else { - // Error occurred + if (error != nullptr) { self->m_selectedIsoPath.clear(); - self->m_errorString = fmt::format("File dialog error: {}", SDL_GetError()); + self->m_errorString = fmt::format("File dialog error: {}", error); + return; } + + if (path == nullptr) { + self->m_selectedIsoPath.clear(); + return; + } + + self->m_selectedIsoPath = path; + getSettings().backend.isoPath.setValue(self->m_selectedIsoPath); + config::Save(); } ImGuiPreLaunchWindow::ImGuiPreLaunchWindow() = default; @@ -144,9 +136,9 @@ void ImGuiPreLaunchWindow::drawMainMenu() { } if (ImGuiButtonCenter("Select disc image...")) { - SDL_ShowOpenFileDialog(&fileDialogCallback, this, aurora::window::get_sdl_window(), - skGameDiscFileFilters.data(), int(skGameDiscFileFilters.size()), - nullptr, false); + ShowFileSelect(&fileDialogCallback, this, aurora::window::get_sdl_window(), + skGameDiscFileFilters.data(), int(skGameDiscFileFilters.size()), nullptr, + false); } } else { if (ImGuiButtonCenter("Start game")) { @@ -187,9 +179,9 @@ void ImGuiPreLaunchWindow::drawOptions() { ImGui::InputText("Game ISO Path", &m_selectedIsoPath, ImGuiInputTextFlags_ReadOnly); ImGui::SameLine(); if (ImGui::Button("Set")) { - SDL_ShowOpenFileDialog(&fileDialogCallback, this, aurora::window::get_sdl_window(), - skGameDiscFileFilters.data(), int(skGameDiscFileFilters.size()), - nullptr, false); + ShowFileSelect(&fileDialogCallback, this, aurora::window::get_sdl_window(), + skGameDiscFileFilters.data(), int(skGameDiscFileFilters.size()), nullptr, + false); } AuroraBackend configuredBackend = BACKEND_AUTO; diff --git a/src/dusk/imgui/ImGuiProcessOverlay.cpp b/src/dusk/imgui/ImGuiProcessOverlay.cpp index 04a87f00b6..ed71e6c485 100644 --- a/src/dusk/imgui/ImGuiProcessOverlay.cpp +++ b/src/dusk/imgui/ImGuiProcessOverlay.cpp @@ -9,53 +9,109 @@ #include "f_pc/f_pc_layer_iter.h" #include "f_pc/f_pc_leaf.h" #include "f_pc/f_pc_node.h" +#include "d/d_debug_viewer.h" #include "imgui.h" #include "ImGuiConsole.hpp" #include "ImGuiMenuTools.hpp" #include "imgui_internal.h" namespace dusk { - bool showTreeRecursive; + static bool BeginProcTable() { + static ImGuiTableFlags table_flags = + ImGuiTableFlags_BordersV | ImGuiTableFlags_BordersOuterH | ImGuiTableFlags_Resizable | + ImGuiTableFlags_RowBg | ImGuiTableFlags_NoBordersInBody; + + if (ImGui::BeginTable("proc_table", 7)) { + ImGui::TableSetupColumn("ID", ImGuiTableColumnFlags_WidthFixed, 128); + ImGui::TableSetupColumn("En", ImGuiTableColumnFlags_WidthFixed, 32); + ImGui::TableSetupColumn("Vs", ImGuiTableColumnFlags_WidthFixed, 32); + ImGui::TableSetupColumn("ProcName"); + ImGui::TableSetupColumn("Param", ImGuiTableColumnFlags_WidthFixed, 128); + ImGui::TableSetupColumn("Pi", ImGuiTableColumnFlags_WidthFixed, 64); + ImGui::TableSetupColumn("DwPi", ImGuiTableColumnFlags_WidthFixed, 64); + ImGui::TableHeadersRow(); + return true; + } + return false; + } static int ShowProcess(void* p, void*) { auto proc = static_cast(p); - char buf[64]; - snprintf(buf, sizeof(buf), "%d", proc->id); + ImGui::TableNextRow(); + ImGui::PushID(proc); + bool pending = proc->create_req != nullptr; + if (pending) + ImGui::PushStyleColor(ImGuiCol_Text, {255, 200, 0, 255}); + ImGui::TableNextColumn(); - ImVec2 avail = ImGui::GetContentRegionAvail(); + char id_buf[32]; + sprintf(id_buf, "%d", proc->id); - ImVec2 vec = { avail.x, 0 }; - if (ImGui::BeginChild(buf, vec, ImGuiChildFlags_Border | ImGuiChildFlags_AutoResizeY)) { - ImGui::Text("[%d] %s", proc->id, GetProcName(proc->profname)); - ImGui::Text("init_state: %d, create_phase: %d", proc->state.init_state, proc->state.create_phase); + int flags = ImGuiTreeNodeFlags_SpanAllColumns; + bool isLayer = fpcBs_Is_JustOfType(g_fpcNd_type, proc->subtype); + if (isLayer) { + flags |= ImGuiTreeNodeFlags_DefaultOpen; + } else { + flags |= ImGuiTreeNodeFlags_Leaf | ImGuiTreeNodeFlags_NoTreePushOnOpen; + } - const char* ofTypeName = "unknown"; - if (proc->subtype == g_fpcNd_type) { - ofTypeName = "Node"; - } - else if (proc->subtype == g_fpcLf_type) { - ofTypeName = "Leaf"; - } + bool open = ImGui::TreeNodeEx(id_buf, flags); + fopAc_ac_c* ac = fopAcM_IsActor(proc) ? (fopAc_ac_c*)proc : nullptr; + if (ac != nullptr && ImGui::IsItemHovered()) { + fopAcM_DrawCullingBox(ac, {0, 255, 255, 255}); + } - ImGui::Text("OfType: %d (%s), layer: %d", proc->subtype, ofTypeName, proc->layer_tag.layer->layer_id); + ImGui::TableNextColumn(); + bool enable = !fpcM_IsPause(proc, 1); + if (ImGui::Checkbox("##enable", &enable)) { + if (enable) + fpcM_PauseDisable(proc, 1); + else + fpcM_PauseEnable(proc, 1); + } - if (proc->create_req != nullptr) { - ImGui::TextColored(ImVec4(1.0f, 0.0f, 0.0f, 1.0f), "Pending create request"); - } - - if (showTreeRecursive) { - if (fpcBs_Is_JustOfType(g_fpcNd_type, proc->subtype)) { - auto procNode = static_cast(p); - - ImGui::Text("Owns layer %d", procNode->layer.layer_id); - - fpcLyIt_OnlyHere(&procNode->layer, ShowProcess, nullptr); - } + ImGui::TableNextColumn(); + if (fpcBs_Is_JustOfType(g_fpcLf_type, proc->subtype)) { + leafdraw_class* lf = (leafdraw_class*)proc; + bool vis = lf->unk_0xBC == 0; + if (ImGui::Checkbox("##visible", &vis)) { + if (vis) + lf->unk_0xBC = 0; + else + lf->unk_0xBC = 1; } } - ImGui::EndChild(); + ImGui::TableNextColumn(); + ImGui::Text("%s", ac != nullptr ? fopAcM_getProcNameString(ac) : GetProcName(proc->profname)); + + ImGui::TableNextColumn(); + if (proc->profname == fpcNm_ROOM_SCENE_e) { + ImGui::Text("Room %d", proc->parameters); + } else { + ImGui::Text("%08x", proc->parameters); + } + + ImGui::TableNextColumn(); + ImGui::Text("%d", proc->priority.current_info.list_id); + + ImGui::TableNextColumn(); + if (fpcBs_Is_JustOfType(g_fpcLf_type, proc->subtype)) { + ImGui::Text("%d", fpcM_DrawPriority(proc)); + } else { + ImGui::Text("--"); + } + + if (isLayer && open) { + auto procNode = static_cast(p); + fpcLyIt_OnlyHere(&procNode->layer, ShowProcess, nullptr); + ImGui::TreePop(); + } + + if (pending) + ImGui::PopStyleColor(1); + ImGui::PopID(); return 1; } @@ -76,15 +132,11 @@ namespace dusk { if (ImGui::Begin("Processes", &m_showProcessManagement)) { if (ImGui::BeginTabBar("Tabs")) { - showTreeRecursive = true; if (ImGui::BeginTabItem("Tree")) { - fpcLyIt_OnlyHere(fpcLy_RootLayer(), ShowProcess, nullptr); - ImGui::EndTabItem(); - } - - showTreeRecursive = false; - if (ImGui::BeginTabItem("All layers")) { - fpcLyIt_All(ShowProcess, nullptr); + if (BeginProcTable()) { + fpcLyIt_OnlyHere(fpcLy_RootLayer(), ShowProcess, nullptr); + ImGui::EndTable(); + } ImGui::EndTabItem(); } diff --git a/src/dusk/imgui/ImGuiSaveEditor.cpp b/src/dusk/imgui/ImGuiSaveEditor.cpp index 1618998249..5802c07b47 100644 --- a/src/dusk/imgui/ImGuiSaveEditor.cpp +++ b/src/dusk/imgui/ImGuiSaveEditor.cpp @@ -4,6 +4,7 @@ #include "ImGuiConsole.hpp" #include "ImGuiSaveEditor.hpp" +#include "ImGuiEventFlags.hpp" #include "d/d_com_inf_game.h" #include "d/d_item_data.h" @@ -815,46 +816,66 @@ namespace dusk { void ImGuiSaveEditor::drawInventoryTab() { dSv_player_item_c& item = dComIfGs_getSaveData()->getPlayer().getItem(); - if (ImGui::Button("Default All##inv_default_all")) { - for (int slot = 0; slot < 24; slot++) { - dComIfGs_setItem(slot, getSlotDefault(slot)); + if (ImGui::TreeNode("Item Wheel")) { + if (ImGui::Button("Default All##inv_default_all")) { + for (int slot = 0; slot < 24; slot++) { + dComIfGs_setItem(slot, getSlotDefault(slot)); + } } - } - ImGui::SameLine(); - if (ImGui::Button("Clear All##inv_clear_all")) { - for (int slot = 0; slot < 24; slot++) { - dComIfGs_setItem(slot, dItemNo_NONE_e); - } - } - - ImGuiBeginGroupPanel("Items", { 200, 100 }); - for (int slot = 0; slot < 24; slot++) { - ImGui::Text("Slot %02d (%s): ", slot, itemMap.find(getSlotDefault(slot))->second.m_name.c_str()); - ImGui::SameLine(240.0f); - if (ImGui::BeginCombo(fmt::format("##ItemComboBox{}", slot).c_str(), itemMap.find(item.mItems[slot])->second.m_name.c_str())) { - if (ImGui::Selectable("None")) { + ImGui::SameLine(); + if (ImGui::Button("Clear All##inv_clear_all")) { + for (int slot = 0; slot < 24; slot++) { dComIfGs_setItem(slot, dItemNo_NONE_e); } + } - for (int i = 0; i < 254; i++) { - if (itemMap.find(i)->second.m_type != ITEMTYPE_EQUIP_e) continue; - - if (ImGui::Selectable(fmt::format("{0}##item_{1}{2}", itemMap.find(i)->second.m_name, slot, i).c_str())) { - dComIfGs_setItem(slot, itemMap.find(i)->first); + ImGuiBeginGroupPanel("Items", { 200, 100 }); + for (int slot = 0; slot < 24; slot++) { + ImGui::Text("Slot %02d (%s): ", slot, itemMap.find(getSlotDefault(slot))->second.m_name.c_str()); + ImGui::SameLine(240.0f); + if (ImGui::BeginCombo(fmt::format("##ItemComboBox{}", slot).c_str(), itemMap.find(item.mItems[slot])->second.m_name.c_str())) { + if (ImGui::Selectable("None")) { + dComIfGs_setItem(slot, dItemNo_NONE_e); } + + for (int i = 0; i < 254; i++) { + if (itemMap.find(i)->second.m_type != ITEMTYPE_EQUIP_e) continue; + + if (ImGui::Selectable(fmt::format("{0}##item_{1}{2}", itemMap.find(i)->second.m_name, slot, i).c_str())) { + dComIfGs_setItem(slot, itemMap.find(i)->first); + } + } + ImGui::EndCombo(); + } + ImGui::SameLine(); + if (ImGui::SmallButton(fmt::format("Default##slot_d_{}", slot).c_str())) { + dComIfGs_setItem(slot, getSlotDefault(slot)); + } + ImGui::SameLine(); + if (ImGui::SmallButton(fmt::format("Clear##slot_c_{}", slot).c_str())) { + dComIfGs_setItem(slot, dItemNo_NONE_e); } - ImGui::EndCombo(); - } - ImGui::SameLine(); - if (ImGui::SmallButton(fmt::format("Default##slot_d_{}", slot).c_str())) { - dComIfGs_setItem(slot, getSlotDefault(slot)); - } - ImGui::SameLine(); - if (ImGui::SmallButton(fmt::format("Clear##slot_c_{}", slot).c_str())) { - dComIfGs_setItem(slot, dItemNo_NONE_e); } + ImGuiEndGroupPanel(); + + ImGui::TreePop(); + } + + if (ImGui::TreeNode("Get Item Flags")) { + for (int i = 0; i < 254; i++) { + if (itemMap.find(i)->second.m_name == "Reserved") continue; + + bool flag = dComIfGs_isItemFirstBit(i); + if (ImGui::Checkbox(fmt::format("{0}##item_{1}", itemMap.find(i)->second.m_name, i).c_str(), &flag)) { + if (flag) + dComIfGs_onItemFirstBit(i); + else + dComIfGs_offItemFirstBit(i); + } + } + + ImGui::TreePop(); } - ImGuiEndGroupPanel(); dSv_player_item_record_c& itemRecord = dComIfGs_getSaveData()->getPlayer().getItemRecord(); dSv_player_item_max_c& itemMax = dComIfGs_getSaveData()->getPlayer().getItemMax(); @@ -1263,6 +1284,16 @@ namespace dusk { } } + static inline void genDungeonItemCheckbox(dSv_memBit_c& membit, const char* label, int flag) { + bool tempFlag = membit.isDungeonItem(flag); + if (ImGui::Checkbox(label, &tempFlag)) { + if (tempFlag) + membit.onDungeonItem(flag); + else + membit.offDungeonItem(flag); + } + } + void genMembitFlags(const char* id, dSv_memBit_c& membit) { ImGuiBeginGroupPanel("Chest", { 100, 100 }); for (int j = 0; j < 2; j++) { @@ -1270,7 +1301,7 @@ namespace dusk { } ImGuiEndGroupPanel(); - ImVec2 start_cursor = ImGui::GetCursorPos(); + ImVec2 post_tbox_cursor = ImGui::GetCursorPos(); ImGui::SameLine(); @@ -1280,9 +1311,9 @@ namespace dusk { } ImGuiEndGroupPanel(); - ImVec2 cursor = ImGui::GetCursorPos(); + ImVec2 post_switch_cursor = ImGui::GetCursorPos(); - ImGui::SetCursorPos(start_cursor); + ImGui::SetCursorPos(post_tbox_cursor); ImGuiBeginGroupPanel("Item", { 100, 100 }); for (int j = 0; j < 1; j++) { @@ -1290,7 +1321,28 @@ namespace dusk { } ImGuiEndGroupPanel(); - ImGui::SetCursorPos({ start_cursor.x, cursor.y }); + ImGui::SetCursorPosY(ImGui::GetCursorPosY() + 10.0f); + + genDungeonItemCheckbox(membit, "Got Map", dSv_memBit_c::MAP); + ImGui::SameLine(230.0f); + genDungeonItemCheckbox(membit, "Got Compass", dSv_memBit_c::COMPASS); + + genDungeonItemCheckbox(membit, "Got Boss Key", dSv_memBit_c::BOSS_KEY); + ImGui::SameLine(230.0f); + genDungeonItemCheckbox(membit, "Saw Boss Demo", dSv_memBit_c::STAGE_BOSS_DEMO); + + genDungeonItemCheckbox(membit, "Got Heart Container", dSv_memBit_c::STAGE_LIFE); + ImGui::SameLine(230.0f); + genDungeonItemCheckbox(membit, "Defeated Boss", dSv_memBit_c::STAGE_BOSS_ENEMY); + + genDungeonItemCheckbox(membit, "Defeated Miniboss", dSv_memBit_c::STAGE_BOSS_ENEMY_2); + ImGui::SameLine(230.0f); + genDungeonItemCheckbox(membit, "Got Ooccoo", dSv_memBit_c::OOCCOO_NOTE); + + int keyTemp = membit.getKeyNum(); + if (ImGui::SliderInt("Keys", &keyTemp, 0, 5)) { + membit.setKeyNum(keyTemp); + } } void ImGuiSaveEditor::drawFlagsTab() { @@ -1298,15 +1350,18 @@ namespace dusk { dSv_memBit_c& membit = g_dComIfG_gameInfo.info.mMemory.mBit; genMembitFlags("##TempSceneFlags", membit); - int stageNo = dStage_stagInfo_GetSaveTbl(dComIfGp_getStageStagInfo()); - if (ImGui::Button("Save##SaveTempFlags")) { - dComIfGs_putSave(stageNo); - } + stage_stag_info_class* pstag = dComIfGp_getStageStagInfo(); + if (pstag != nullptr) { + int stageNo = dStage_stagInfo_GetSaveTbl(pstag); + if (ImGui::Button("Save##SaveTempFlags")) { + dComIfGs_putSave(stageNo); + } - ImGui::SameLine(); + ImGui::SameLine(); - if (ImGui::Button("Load##LoadSaveFlags")) { - dComIfGs_getSave(stageNo); + if (ImGui::Button("Load##LoadSaveFlags")) { + dComIfGs_getSave(stageNo); + } } ImGui::TreePop(); @@ -1319,7 +1374,7 @@ namespace dusk { "Faron", "Eldin", "Lanayru", - "Unknown", + "Reserved", "Hyrule Field", "Sacred Grove", "Snowpeak", @@ -1365,20 +1420,130 @@ namespace dusk { if (ImGui::TreeNode("Event Flags")) { dSv_event_c& event = dComIfGs_getSaveData()->mEvent; - for (int e = 0; e < 255; e++) { - ImGui::Text("%03d ", e); - ImGui::SameLine(80.0f); - for (int i = 7; i >= 0; i--) { - bool flag = event.mEvent[e] & (1 << i); - if (ImGui::Checkbox(fmt::format("##event{0}{1}", e, i).c_str(), &flag)) { - if (flag) - event.mEvent[e] |= (1 << i); - else - event.mEvent[e] &= ~(1 << i); + + static ImGuiTextFilter filter; + filter.Draw(); // Search bar + + ImVec2 flagTableSize = {700, 400}; + if (ImGui::BeginTable("Events", 4, + ImGuiTableFlags_ScrollY | ImGuiTableFlags_ScrollX | ImGuiTableFlags_Sortable, + flagTableSize)) + { + ImGui::TableSetupScrollFreeze(0, 1); + constexpr int COLUMN_FLAG = 0, COLUMN_NAME = 1, COLUMN_LOC = 2, COLUMN_DESC = 3; + ImGui::TableSetupColumn("Flag"); + ImGui::TableSetupColumn("Name"); + ImGui::TableSetupColumn("Location"); + ImGui::TableSetupColumn("Description"); + ImGui::TableHeadersRow(); + + // if we're sorting by whether the flag is set or not, + // we want to re-sort whenever a flag updates, which means every frame cuz we don't know when it changes. + // otherwise only re-sort when the sort is dirty + if (auto* sort = ImGui::TableGetSortSpecs(); + sort != nullptr && sort->SpecsCount > 0 && + (sort->SpecsDirty || sort->Specs[0].ColumnIndex == COLUMN_FLAG)) + { + auto column = sort->Specs->ColumnIndex; + const auto cmp = [&](const duskImguiEventFlagEntry& l, + const duskImguiEventFlagEntry& r) -> bool { + switch (column) { + case COLUMN_FLAG: + return (bool)event.isEventBit(l.flagID) < + (bool)event.isEventBit(r.flagID); + case COLUMN_NAME: + return l.flagName < r.flagName; + case COLUMN_LOC: + return l.location < r.location; + case COLUMN_DESC: + return l.description < r.description; + } + return false; + }; + + const auto direction = sort->Specs[0].SortDirection; + + if (direction == ImGuiSortDirection_Ascending) { + std::sort(std::begin(duskImguiEventFlags), std::end(duskImguiEventFlags), cmp); + } else { + std::sort(std::rbegin(duskImguiEventFlags), std::rend(duskImguiEventFlags), cmp); } - ImGui::SameLine(); + sort->SpecsDirty = false; } - ImGui::NewLine(); + + for (const auto& e : duskImguiEventFlags) { + if (!filter.PassFilter((e.location + "\n" + e.description + "\n" + e.flagName).c_str())) + { + continue; + } + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + bool flag = event.getEventReg(e.flagID); + if (ImGui::Checkbox(("##" + e.flagName).c_str(), &flag)) { + if (flag) { + event.onEventBit(e.flagID); + } else { + event.offEventBit(e.flagID); + } + } + + ImGui::TableNextColumn(); + ImGui::Text(e.flagName.c_str()); + ImGui::TableNextColumn(); + ImGui::Text(e.location.c_str()); + ImGui::TableNextColumn(); + ImGui::Text(e.description.c_str()); + } + ImGui::EndTable(); + } + + // event values that are stored as u8s in the event flags + for (const auto& e : duskImguiU8Events) { + int v = event.mEvent[e.byteInd]; + if (ImGui::InputInt(e.description, &v)) { + v = std::clamp(v, 0, 0xff); + event.mEvent[e.byteInd] = (u8)v; + } + } + + // event values that are stored as u16s in the event flags + for (const auto& e : duskImguiU16Events) { + int v = (event.mEvent[e.byteInd] << 8) | event.mEvent[e.byteInd + 1]; + if (ImGui::InputInt(e.description, &v)) { + v = std::clamp(v, 0, 0xffff); + event.mEvent[e.byteInd] = (u8)(v >> 8); + event.mEvent[e.byteInd + 1] = (u8)v; + } + } + + // event values that are stored as swapped u16s in the event flags + for (const auto& e : duskImguiSwappedU16Events) { + int v = (event.mEvent[e.byteInd + 1] << 8) | event.mEvent[e.byteInd]; + if (ImGui::InputInt(e.description, &v)) { + v = std::clamp(v, 0, 0xffff); + event.mEvent[e.byteInd + 1] = (u8)(v >> 8); + event.mEvent[e.byteInd] = (u8)v; + } + } + + if (ImGui::TreeNode("Event Matrix")) { + for (int e = 0; e < 255; e++) { + ImGui::Text("%03d ", e); + ImGui::SameLine(80.0f); + for (int i = 7; i >= 0; i--) { + bool flag = event.mEvent[e] & (1 << i); + if (ImGui::Checkbox(fmt::format("##event{0}{1}", e, i).c_str(), &flag)) { + if (flag) + event.mEvent[e] |= (1 << i); + else + event.mEvent[e] &= ~(1 << i); + } + ImGui::SameLine(); + } + ImGui::NewLine(); + } + ImGui::TreePop(); } ImGui::TreePop(); } @@ -1420,4 +1585,4 @@ namespace dusk { ImGui::EndCombo(); } } -} \ No newline at end of file +} diff --git a/src/dusk/imgui/ImGuiStateShare.cpp b/src/dusk/imgui/ImGuiStateShare.cpp index 172aad17a5..9d88181b10 100644 --- a/src/dusk/imgui/ImGuiStateShare.cpp +++ b/src/dusk/imgui/ImGuiStateShare.cpp @@ -5,14 +5,22 @@ #include "imgui.h" #include "fmt/format.h" #include "absl/strings/escaping.h" +#include "nlohmann/json.hpp" #include "d/d_com_inf_game.h" #include "dusk/main.h" +#include "dusk/io.hpp" +#include "dusk/logging.h" +#include "../file_select.hpp" +#include "aurora/lib/window.hpp" +#include #include namespace dusk { +using json = nlohmann::json; + #pragma pack(push, 1) struct StateSharePacket { char stageName[8]; @@ -23,9 +31,65 @@ struct StateSharePacket { }; #pragma pack(pop) -static constexpr size_t PACKET_TOTAL = sizeof(StateSharePacket) + sizeof(dSv_info_c); +static constexpr size_t PACKET_TOTAL = sizeof(StateSharePacket) + sizeof(dSv_info_c); +static constexpr size_t PACKET_SAVE_ONLY = sizeof(StateSharePacket) + sizeof(dSv_save_c); +static constexpr auto STATES_FILENAME = "states.json"; -void ImGuiStateShare::copyState() { +static bool ValidateEncodedState(const std::string&); + +void ImGuiStateShare::onMergeFileSelected(void* userdata, const char* path, const char* /*error*/) { + auto* self = static_cast(userdata); + if (path != nullptr) { + self->m_pendingMergePath = path; + } +} + + + +static std::string GetStatesFilePath() { + return (dusk::ConfigPath / STATES_FILENAME).string(); +} + +void ImGuiStateShare::loadStatesFile() { + m_loaded = true; + const std::filesystem::path filePath = dusk::ConfigPath / STATES_FILENAME; + if (!std::filesystem::exists(filePath)) { + return; + } + try { + const std::string pathStr = filePath.string(); + auto data = io::FileStream::ReadAllBytes(pathStr.c_str()); + auto j = json::parse(data); + if (!j.is_array()) { + return; + } + for (const auto& entry : j) { + if (!entry.contains("name") || !entry.contains("data")) { + continue; + } + SavedStateEntry s; + s.name = entry["name"].get(); + s.encoded = entry["data"].get(); + m_states.push_back(std::move(s)); + } + } catch (const std::exception& e) { + m_statusMsg = fmt::format("Failed to load states: {}", e.what()); + } +} + +void ImGuiStateShare::saveStatesFile() { + json j = json::array(); + for (const auto& s : m_states) { + j.push_back(json{{"name", s.name}, {"data", s.encoded}}); + } + try { + io::FileStream::WriteAllText(GetStatesFilePath().c_str(), j.dump(2)); + } catch (const std::exception& e) { + m_statusMsg = fmt::format("Failed to save states: {}", e.what()); + } +} + +std::string ImGuiStateShare::encodeCurrentState() { StateSharePacket pkt = {}; strncpy(pkt.stageName, dComIfGp_getStartStageName(), 7); pkt.roomNo = dComIfGp_getStartStageRoomNo(); @@ -40,26 +104,25 @@ void ImGuiStateShare::copyState() { std::string compressed(bound, '\0'); compressed.resize(ZSTD_compress(compressed.data(), bound, raw.data(), raw.size(), 1)); - std::string encoded = absl::Base64Escape(compressed); - ImGui::SetClipboardText(encoded.c_str()); - m_statusMsg = "Copied to clipboard."; + return absl::Base64Escape(compressed); } -bool ImGuiStateShare::pasteState() { - const char* clip = ImGui::GetClipboardText(); - if (!clip || clip[0] == '\0') { - m_statusMsg = "Clipboard is empty."; - return false; - } - +bool ImGuiStateShare::applyEncodedState(const std::string& encoded, const std::string& name) { std::string decoded; - if (!absl::Base64Unescape(clip, &decoded)) { + if (!absl::Base64Unescape(encoded, &decoded)) { m_statusMsg = "Invalid base64."; return false; } unsigned long long dSize = ZSTD_getFrameContentSize(decoded.data(), decoded.size()); - if (dSize == ZSTD_CONTENTSIZE_ERROR || dSize == ZSTD_CONTENTSIZE_UNKNOWN || dSize < PACKET_TOTAL) { + if (dSize == ZSTD_CONTENTSIZE_ERROR || dSize == ZSTD_CONTENTSIZE_UNKNOWN) { + m_statusMsg = "Not a valid state string."; + return false; + } + + const bool isFull = (dSize == PACKET_TOTAL); + const bool isPartial = (dSize == PACKET_SAVE_ONLY); + if (!isFull && !isPartial) { m_statusMsg = "Not a valid state string."; return false; } @@ -75,45 +138,261 @@ bool ImGuiStateShare::pasteState() { memcpy(&pkt, raw.data(), sizeof(pkt)); pkt.stageName[7] = '\0'; - memcpy(&g_dComIfG_gameInfo.info, raw.data() + sizeof(pkt), sizeof(dSv_info_c)); + if (isFull) { + memcpy(&g_dComIfG_gameInfo.info, raw.data() + sizeof(pkt), sizeof(dSv_info_c)); + m_pendingInfo = g_dComIfG_gameInfo.info; + m_pendingSavedata.reset(); + } else { + memcpy(&g_dComIfG_gameInfo.info.mSavedata, raw.data() + sizeof(pkt), sizeof(dSv_save_c)); + m_pendingSavedata = g_dComIfG_gameInfo.info.mSavedata; + m_pendingInfo.reset(); + } s16 spawnPoint = pkt.startPoint == -4 ? -1 : pkt.startPoint; - if (spawnPoint == -1) { dComIfGs_setRestartRoomParam(pkt.roomNo & 0x3F); } dComIfGp_setNextStage(pkt.stageName, spawnPoint, pkt.roomNo, pkt.layer); - m_pendingInfo = g_dComIfG_gameInfo.info; - m_statusMsg = fmt::format("Warping to {} room {} layer {}.", pkt.stageName, (int)pkt.roomNo, (int)pkt.layer); + if (name.empty()) { + m_statusMsg = fmt::format("{} room {} layer {}.", pkt.stageName, (int)pkt.roomNo, (int)pkt.layer); + } else { + m_statusMsg = fmt::format("{}: {} room {} layer {}.", name, pkt.stageName, (int)pkt.roomNo, (int)pkt.layer); + } return true; } void ImGuiStateShare::tickPendingApply() { - if (!m_pendingInfo.has_value() || dComIfGp_isEnableNextStage()) + if (!m_pendingInfo.has_value() && !m_pendingSavedata.has_value()) { return; - g_dComIfG_gameInfo.info = *m_pendingInfo; - m_pendingInfo.reset(); + } + if (dComIfGp_isEnableNextStage()) { + return; + } + if (m_pendingInfo.has_value()) { + g_dComIfG_gameInfo.info = *m_pendingInfo; + m_pendingInfo.reset(); + } else { + g_dComIfG_gameInfo.info.mSavedata = *m_pendingSavedata; + m_pendingSavedata.reset(); + } + dComIfGp_offOxygenShowFlag(); + dComIfGp_setMaxOxygen(600); + dComIfGp_setOxygen(600); +} + +static bool ValidateEncodedState(const std::string& encoded) { + std::string decoded; + if (!absl::Base64Unescape(encoded, &decoded)) { + return false; + } + unsigned long long dSize = ZSTD_getFrameContentSize(decoded.data(), decoded.size()); + return dSize == PACKET_TOTAL || dSize == PACKET_SAVE_ONLY; +} + +void ImGuiStateShare::mergeFromFile(const std::string& path) { + try { + auto data = io::FileStream::ReadAllBytes(path.c_str()); + auto j = json::parse(data); + if (!j.is_array()) { + m_statusMsg = "File does not contain a JSON array."; + return; + } + + std::unordered_set existingNames; + for (const auto& s : m_states) { + existingNames.insert(s.name); + } + + int added = 0; + int skipped = 0; + for (const auto& entry : j) { + if (!entry.contains("name") || !entry.contains("data")) { + ++skipped; + continue; + } + const std::string name = entry["name"].get(); + const std::string encoded = entry["data"].get(); + if (!ValidateEncodedState(encoded)) { + ++skipped; + continue; + } + if (existingNames.count(name)) { + ++skipped; + continue; + } + SavedStateEntry s; + s.name = name; + s.encoded = encoded; + existingNames.insert(s.name); + m_states.push_back(std::move(s)); + ++added; + } + + if (added > 0) { + saveStatesFile(); + } + m_statusMsg = fmt::format("Merged: {} added, {} skipped.", added, skipped); + } catch (const std::exception& e) { + m_statusMsg = fmt::format("Failed to load file: {}", e.what()); + } } void ImGuiStateShare::draw(bool& open) { - if (dusk::IsGameLaunched) + if (dusk::IsGameLaunched) { tickPendingApply(); + } - if (!open) + if (!m_loaded) { + loadStatesFile(); + } + + if (!m_pendingMergePath.empty()) { + mergeFromFile(m_pendingMergePath); + m_pendingMergePath.clear(); + } + + if (!open) { return; + } + ImGui::SetNextWindowSizeConstraints(ImVec2(400, 0), ImVec2(FLT_MAX, FLT_MAX)); if (!ImGui::Begin("State Share", &open, ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoFocusOnAppearing | ImGuiWindowFlags_NoNav)) { ImGui::End(); return; } - if (!dusk::IsGameLaunched) ImGui::BeginDisabled(); - if (ImGui::Button("Copy State")) copyState(); + const bool gameRunning = dusk::IsGameLaunched; + + const float rowH = ImGui::GetTextLineHeightWithSpacing(); + const float listH = rowH * 8 + ImGui::GetStyle().FramePadding.y * 2; + ImGui::BeginChild("##states", ImVec2(0, listH), true); + + if (m_states.empty()) { + ImGui::TextDisabled("No saved states. Save or import one below."); + } + + int toDelete = -1; + for (int i = 0; i < (int)m_states.size(); ++i) { + ImGui::PushID(i); + + if (m_renamingIndex == i) { + ImGui::SetNextItemWidth(150); + bool done = ImGui::InputText("##rename", m_renameBuffer, sizeof(m_renameBuffer), + ImGuiInputTextFlags_EnterReturnsTrue | ImGuiInputTextFlags_AutoSelectAll); + if (done) { + if (m_renameBuffer[0] != '\0') { + m_states[i].name = m_renameBuffer; + } + m_renamingIndex = -1; + saveStatesFile(); + } else if (ImGui::IsItemDeactivated()) { + m_renamingIndex = -1; + } + } else { + ImGui::Selectable(m_states[i].name.c_str(), false, ImGuiSelectableFlags_None, ImVec2(150, 0)); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Double-click to rename"); + if (ImGui::IsMouseDoubleClicked(0)) { + m_renamingIndex = i; + strncpy(m_renameBuffer, m_states[i].name.c_str(), sizeof(m_renameBuffer) - 1); + m_renameBuffer[sizeof(m_renameBuffer) - 1] = '\0'; + ImGui::SetKeyboardFocusHere(-1); + } + } + } + + ImGui::SameLine(); + if (!gameRunning) { ImGui::BeginDisabled(); } + if (ImGui::Button("Load")) { + applyEncodedState(m_states[i].encoded, m_states[i].name); + } + if (!gameRunning) { ImGui::EndDisabled(); } + + ImGui::SameLine(); + if (ImGui::Button("Copy")) { + ImGui::SetClipboardText(m_states[i].encoded.c_str()); + m_statusMsg = fmt::format("'{}' copied to clipboard.", m_states[i].name); + } + + ImGui::SameLine(); + if (ImGui::Button("Del")) { + toDelete = i; + } + + ImGui::PopID(); + } + + if (toDelete >= 0) { + if (m_renamingIndex == toDelete) { m_renamingIndex = -1; } + m_states.erase(m_states.begin() + toDelete); + saveStatesFile(); + } + + ImGui::EndChild(); + + // Toolbar + if (!gameRunning) { ImGui::BeginDisabled(); } + if (ImGui::Button("Save")) { + SavedStateEntry entry; + entry.name = fmt::format("State {}", m_states.size() + 1); + entry.encoded = encodeCurrentState(); + m_states.push_back(std::move(entry)); + saveStatesFile(); + m_statusMsg = fmt::format("Saved as '{}'.", m_states.back().name); + } + if (!gameRunning) { ImGui::EndDisabled(); } + ImGui::SameLine(); - if (ImGui::Button("Import State")) pasteState(); - if (!dusk::IsGameLaunched) ImGui::EndDisabled(); + if (ImGui::Button("Import Clipboard")) { + const char* clip = ImGui::GetClipboardText(); + if (!clip || clip[0] == '\0') { + m_statusMsg = "Clipboard is empty."; + } else { + std::string clipStr = clip; + if (!ValidateEncodedState(clipStr)) { + m_statusMsg = "Clipboard does not contain a valid state."; + } else { + SavedStateEntry entry; + entry.name = fmt::format("Imported {}", m_states.size() + 1); + entry.encoded = std::move(clipStr); + m_states.push_back(std::move(entry)); + saveStatesFile(); + m_statusMsg = fmt::format("Imported as '{}'.", m_states.back().name); + } + } + } + + ImGui::SameLine(); + if (ImGui::Button("Load Pack")) { + static constexpr SDL_DialogFileFilter filter = {"State pack", "json"}; + ShowFileSelect(&onMergeFileSelected, this, aurora::window::get_sdl_window(), &filter, 1, nullptr, false); + } + + if (!m_states.empty()) { + ImGui::SameLine(); + if (ImGui::Button("Clear All")) { + ImGui::OpenPopup("##clearall"); + } + + if (ImGui::BeginPopup("##clearall")) { + ImGui::Text("Delete all saved states?"); + ImGui::Spacing(); + if (ImGui::Button("Yes, clear all")) { + m_states.clear(); + m_renamingIndex = -1; + saveStatesFile(); + m_statusMsg = "All states cleared."; + ImGui::CloseCurrentPopup(); + } + ImGui::SameLine(); + if (ImGui::Button("Cancel")) { + ImGui::CloseCurrentPopup(); + } + ImGui::EndPopup(); + } + } if (!m_statusMsg.empty()) { ImGui::Spacing(); @@ -125,8 +404,9 @@ void ImGuiStateShare::draw(bool& open) { } void ImGuiMenuTools::ShowStateShare() { - if (!ImGuiConsole::CheckMenuViewToggle(ImGuiKey_F8, m_showStateShare)) + if (!ImGuiConsole::CheckMenuViewToggle(ImGuiKey_F8, m_showStateShare)) { return; + } m_stateShare.draw(m_showStateShare); } diff --git a/src/dusk/imgui/ImGuiStateShare.hpp b/src/dusk/imgui/ImGuiStateShare.hpp index a09cfd5963..7739e3db3b 100644 --- a/src/dusk/imgui/ImGuiStateShare.hpp +++ b/src/dusk/imgui/ImGuiStateShare.hpp @@ -4,21 +4,38 @@ #include "d/d_save.h" #include #include +#include namespace dusk { - class ImGuiStateShare { - public: - void draw(bool& open); - private: - void copyState(); - bool pasteState(); - void tickPendingApply(); +struct SavedStateEntry { + std::string name; + std::string encoded; +}; + +class ImGuiStateShare { +public: + void draw(bool& open); + +private: + std::string encodeCurrentState(); + bool applyEncodedState(const std::string& encoded, const std::string& name = {}); + void tickPendingApply(); + void loadStatesFile(); + void saveStatesFile(); + void mergeFromFile(const std::string& path); + static void onMergeFileSelected(void* userdata, const char* path, const char* error); + + std::vector m_states; + std::string m_statusMsg; + std::optional m_pendingInfo; + std::optional m_pendingSavedata; + int m_renamingIndex = -1; + char m_renameBuffer[128] = {}; + bool m_loaded = false; + std::string m_pendingMergePath; +}; - std::string m_statusMsg; - std::optional m_pendingInfo; - }; } #endif - \ No newline at end of file diff --git a/src/dusk/ios/FileSelectDialog.h b/src/dusk/ios/FileSelectDialog.h new file mode 100644 index 0000000000..0495b397b1 --- /dev/null +++ b/src/dusk/ios/FileSelectDialog.h @@ -0,0 +1,21 @@ +#pragma once + +#include + +#include + +struct SDL_Window; + +#ifdef __cplusplus +extern "C" { +#endif + +typedef void (*IOSFileCallback)(void* userdata, const char* path, const char* error); + +void Dusk_iOS_ShowFileSelect(IOSFileCallback callback, void* userdata, SDL_Window* window, + const SDL_DialogFileFilter* filters, int nfilters, + const char* default_location, bool allow_many); + +#ifdef __cplusplus +} +#endif diff --git a/src/dusk/ios/FileSelectDialog.m b/src/dusk/ios/FileSelectDialog.m new file mode 100644 index 0000000000..e8efa91a69 --- /dev/null +++ b/src/dusk/ios/FileSelectDialog.m @@ -0,0 +1,151 @@ +#include "FileSelectDialog.h" + +#import +#import +#import +#import + +#include +#include +#include +#include + +static void *g_picker_delegate_key = &g_picker_delegate_key; + +static void RunOnMainThread(void (^block)(void)) +{ + if ([NSThread isMainThread]) { + block(); + } else { + dispatch_sync(dispatch_get_main_queue(), block); + } +} + +static NSError *MakeError(NSString *message) +{ + return [NSError errorWithDomain:@"org.twilitrealm.dusk.file-select" + code:1 + userInfo:@{NSLocalizedDescriptionKey: message}]; +} + +static UIViewController *FindTopViewController(UIViewController *controller) +{ + UIViewController *current = controller; + while (current.presentedViewController != nil) { + current = current.presentedViewController; + } + return current; +} + +static UIViewController *PresenterFromWindow(SDL_Window *window) +{ + if (window == nil) { + return nil; + } + + const SDL_PropertiesID props = SDL_GetWindowProperties(window); + if (props == 0) { + return nil; + } + + UIWindow *uiwindow = (__bridge UIWindow *)SDL_GetPointerProperty( + props, SDL_PROP_WINDOW_UIKIT_WINDOW_POINTER, NULL); + if (uiwindow == nil || uiwindow.rootViewController == nil) { + return nil; + } + + return FindTopViewController(uiwindow.rootViewController); +} + +static NSURL *InitialDirectoryURL(const char *default_location) +{ + if (default_location == NULL || *default_location == '\0') { + return nil; + } + + NSString *path = [NSString stringWithUTF8String:default_location]; + NSURL *url = [NSURL fileURLWithPath:path]; + if ([path hasSuffix:@"/"]) { + return url; + } + + return [url URLByDeletingLastPathComponent]; +} + +@interface DocumentPickerDelegate : NSObject + +@property(nonatomic, assign) IOSFileCallback callback; +@property(nonatomic, assign) void *userdata; + +@end + +@implementation DocumentPickerDelegate + +- (void)finishWithPath:(const char *)path error:(const char *)error { + if (self.callback != NULL) { + self.callback(self.userdata, path, error); + } +} + +- (void)documentPicker:(UIDocumentPickerViewController *)controller +didPickDocumentsAtURLs:(NSArray *)urls +{ + NSURL *url = urls.firstObject; + if (url == nil) { + [self finishWithPath:NULL error:NULL]; + return; + } + + [self finishWithPath:url.path.UTF8String error:NULL]; + (void)controller; +} + +- (void)documentPickerWasCancelled:(UIDocumentPickerViewController *)controller +{ + [self finishWithPath:NULL error:NULL]; + (void)controller; +} + +@end + +void Dusk_iOS_ShowFileSelect(IOSFileCallback callback, void *userdata, + SDL_Window *window, + const SDL_DialogFileFilter *filters, int nfilters, + const char *default_location, + bool allow_many) +{ + RunOnMainThread(^{ + @autoreleasepool { + UIViewController *presenter = PresenterFromWindow(window); + if (presenter == nil) { + callback(userdata, NULL, "Failed to find an iOS view controller for the file picker."); + return; + } + + NSLog(@"[ShowFileSelect] presenting picker from %@", NSStringFromClass([presenter class])); + + UIDocumentPickerViewController *picker = + [[UIDocumentPickerViewController alloc] + initForOpeningContentTypes:@[ UTTypeItem ] + asCopy:YES]; + picker.allowsMultipleSelection = allow_many ? YES : NO; + picker.shouldShowFileExtensions = YES; + + NSURL *directory_url = InitialDirectoryURL(default_location); + if (directory_url != nil) { + picker.directoryURL = directory_url; + } + + DocumentPickerDelegate *delegate = [DocumentPickerDelegate new]; + delegate.callback = callback; + delegate.userdata = userdata; + picker.delegate = delegate; + objc_setAssociatedObject(picker, g_picker_delegate_key, delegate, + OBJC_ASSOCIATION_RETAIN_NONATOMIC); + + [presenter presentViewController:picker animated:YES completion:nil]; + (void)filters; + (void)nfilters; + } + }); +} diff --git a/src/dusk/logging.cpp b/src/dusk/logging.cpp index 3fdd6cdf45..172059aea4 100644 --- a/src/dusk/logging.cpp +++ b/src/dusk/logging.cpp @@ -168,14 +168,14 @@ void aurora_log_callback(AuroraLogLevel level, const char* module, const char* m aurora::Module DuskLog("dusk"); -void dusk::InitializeFileLogging(const char* configDir, AuroraLogLevel logLevel) { +void dusk::InitializeFileLogging(const std::filesystem::path& configDir, AuroraLogLevel logLevel) { std::lock_guard lock(g_logMutex); - if (g_logFile != nullptr || configDir == nullptr) { + if (g_logFile != nullptr || configDir.empty()) { return; } std::error_code ec; - const std::filesystem::path logsDir = std::filesystem::path(configDir) / "logs"; + const std::filesystem::path logsDir = configDir / "logs"; std::filesystem::create_directories(logsDir, ec); if (ec) { std::fprintf(stderr, "[WARNING | dusk] Failed to create log directory '%s': %s\n", diff --git a/src/dusk/settings.cpp b/src/dusk/settings.cpp index 99ff183e79..aacca0dbc9 100644 --- a/src/dusk/settings.cpp +++ b/src/dusk/settings.cpp @@ -35,19 +35,23 @@ UserSettings g_userSettings = { .noMissClimbing {"game.noMissClimbing", false}, .fastTears {"game.fastTears", false}, .instantSaves {"game.instantSaves", false}, + .instantText {"game.instantText", false}, .sunsSong {"game.sunsSong", false}, // Preferences .enableMirrorMode {"game.enableMirrorMode", false}, .invertCameraXAxis {"game.invertCameraXAxis", false}, .disableMainHUD {"game.disableMainHUD", false}, + .pauseOnFocusLost {"game.pauseOnFocusLost", false}, // Graphics .bloomMode {"game.bloomMode", BloomMode::Classic}, .bloomMultiplier {"game.bloomMultiplier", 1.0f}, - .enableWaterRefraction {"game.enableWaterRefraction", true}, + .disableWaterRefraction {"game.disableWaterRefraction", false}, .enableFrameInterpolation = {"game.enableFrameInterpolation", false}, + .internalResolutionScale {"game.internalResolutionScale", 0}, .shadowResolutionMultiplier {"game.shadowResolutionMultiplier", 1}, + .enableDepthOfField {"game.enableDepthOfField", true}, // Audio .noLowHpSound {"game.noLowHpSound", false}, @@ -55,12 +59,25 @@ UserSettings g_userSettings = { // Input .enableGyroAim {"game.enableGyroAim", false}, - .gyroAimSensitivityX {"game.gyroAimSensitivityX", 1.0f}, - .gyroAimSensitivityY {"game.gyroAimSensitivityY", 1.0f}, - .gyroAimInvertPitch {"game.gyroAimInvertPitch", false}, - .gyroAimInvertYaw {"game.gyroAimInvertYaw", false}, + .enableGyroRollgoal {"game.enableGyroRollgoal", false}, + .gyroSensitivityX {"game.gyroSensitivityX", 1.0f}, + .gyroSensitivityY {"game.gyroSensitivityY", 1.0f}, + .gyroSensitivityRollgoal {"game.gyroSensitivityRollgoal", 1.0f}, + .gyroSmoothing {"game.gyroSmoothing", 0.65f}, + .gyroDeadband {"game.gyroDeadband", 0.04f}, + .gyroInvertPitch {"game.gyroInvertPitch", false}, + .gyroInvertYaw {"game.gyroInvertYaw", false}, // Cheats + .infiniteHearts {"game.infiniteHearts", false}, + .infiniteArrows{"game.infiniteArrows", false}, + .infiniteBombs{"game.infiniteBombs", false}, + .infiniteOil{"game.infiniteOil", false}, + .infiniteOxygen{"game.infiniteOxygen", false}, + .infiniteRupees{"game.infiniteRupees", false}, + .moonJump{"game.moonJump", false}, + .superClawshot{"game.superClawshot", false}, + .alwaysGreatspin{"game.alwaysGreatspin", false}, .enableFastIronBoots {"game.enableFastIronBoots", false}, .canTransformAnywhere {"game.canTransformAnywhere", false}, .fastSpinner {"game.fastSpinner", false}, @@ -79,7 +96,8 @@ UserSettings g_userSettings = { .skipPreLaunchUI {"backend.skipPreLaunchUI", false}, .showPipelineCompilation {"backend.showPipelineCompilation", false}, .wasPresetChosen {"backend.wasPresetChosen", false}, - .enableCrashReporting {"backend.enableCrashReporting", true} + .enableCrashReporting {"backend.enableCrashReporting", true}, + .duskMenuOpen {"backend.duskMenuOpen", false} } }; @@ -115,14 +133,18 @@ void registerSettings() { Register(g_userSettings.game.fastClimbing); Register(g_userSettings.game.fastTears); Register(g_userSettings.game.instantSaves); + Register(g_userSettings.game.instantText); Register(g_userSettings.game.sunsSong); Register(g_userSettings.game.enableMirrorMode); Register(g_userSettings.game.invertCameraXAxis); Register(g_userSettings.game.disableMainHUD); + Register(g_userSettings.game.pauseOnFocusLost); Register(g_userSettings.game.bloomMode); Register(g_userSettings.game.bloomMultiplier); - Register(g_userSettings.game.enableWaterRefraction); + Register(g_userSettings.game.disableWaterRefraction); + Register(g_userSettings.game.internalResolutionScale); Register(g_userSettings.game.shadowResolutionMultiplier); + Register(g_userSettings.game.enableDepthOfField); Register(g_userSettings.game.enableFastIronBoots); Register(g_userSettings.game.canTransformAnywhere); Register(g_userSettings.game.freeMagicArmor); @@ -132,12 +154,25 @@ void registerSettings() { Register(g_userSettings.game.midnasLamentNonStop); Register(g_userSettings.game.enableTurboKeybind); Register(g_userSettings.game.fastSpinner); + Register(g_userSettings.game.infiniteHearts); + Register(g_userSettings.game.infiniteArrows); + Register(g_userSettings.game.infiniteBombs); + Register(g_userSettings.game.infiniteOil); + Register(g_userSettings.game.infiniteOxygen); + Register(g_userSettings.game.infiniteRupees); + Register(g_userSettings.game.moonJump); + Register(g_userSettings.game.superClawshot); + Register(g_userSettings.game.alwaysGreatspin); Register(g_userSettings.game.enableFrameInterpolation); Register(g_userSettings.game.enableGyroAim); - Register(g_userSettings.game.gyroAimSensitivityX); - Register(g_userSettings.game.gyroAimSensitivityY); - Register(g_userSettings.game.gyroAimInvertPitch); - Register(g_userSettings.game.gyroAimInvertYaw); + Register(g_userSettings.game.enableGyroRollgoal); + Register(g_userSettings.game.gyroSensitivityX); + Register(g_userSettings.game.gyroSensitivityY); + Register(g_userSettings.game.gyroSensitivityRollgoal); + Register(g_userSettings.game.gyroDeadband); + Register(g_userSettings.game.gyroSmoothing); + Register(g_userSettings.game.gyroInvertPitch); + Register(g_userSettings.game.gyroInvertYaw); Register(g_userSettings.backend.isoPath); Register(g_userSettings.backend.graphicsBackend); @@ -145,6 +180,7 @@ void registerSettings() { Register(g_userSettings.backend.showPipelineCompilation); Register(g_userSettings.backend.wasPresetChosen); Register(g_userSettings.backend.enableCrashReporting); + Register(g_userSettings.backend.duskMenuOpen); } // Transient settings diff --git a/src/dusk/stubs.cpp b/src/dusk/stubs.cpp index 668541f49c..07e420445c 100644 --- a/src/dusk/stubs.cpp +++ b/src/dusk/stubs.cpp @@ -332,14 +332,6 @@ static VIRetraceCallback sVIPostRetraceCallback = NULL; extern "C" { -void VIConfigure(const GXRenderModeObj* rm) { - STUB_LOG(); -} - -void VIConfigurePan(u16 xOrg, u16 yOrg, u16 width, u16 height) { - STUB_LOG(); -} - u32 VIGetRetraceCount() { return sRetraceCount; } diff --git a/src/f_ap/f_ap_game.cpp b/src/f_ap/f_ap_game.cpp index 9150d764c7..3610f270a3 100644 --- a/src/f_ap/f_ap_game.cpp +++ b/src/f_ap/f_ap_game.cpp @@ -725,7 +725,6 @@ void fapGm_After() { #ifdef TARGET_PC static void fapGm_Before() { - dusk::frame_interp::begin_record_camera(); dusk::frame_interp::begin_record(); } @@ -733,6 +732,66 @@ static void fapGm_AfterRecord() { dusk::frame_interp::end_record(); fapGm_After(); } + +static void duskExecute() { + if (const auto link = g_dComIfG_gameInfo.play.getPlayer(0)) { + dynamic_cast(link)->handleGamepadColor(); + } + + if (mDoCPd_c::getHoldR(PAD_1) && mDoCPd_c::getTrigX(PAD_1)) { + if (const auto link = g_dComIfG_gameInfo.play.getPlayer(0)) { + dynamic_cast(link)->handleWolfHowl(); + } + } + + if ((mDoCPd_c::getHold(PAD_1) & (PAD_TRIGGER_R | PAD_TRIGGER_L)) == PAD_TRIGGER_R && mDoCPd_c::getTrigY(PAD_1)) { + if (const auto link = g_dComIfG_gameInfo.play.getPlayer(0)) { + dynamic_cast(link)->handleQuickTransform(); + } + } + + if (dusk::getSettings().game.moonJump && (mDoCPd_c::getHoldR(PAD_1) && mDoCPd_c::getHoldA(PAD_1))) { + if (const auto link = g_dComIfG_gameInfo.play.getPlayer(0)) { + link->speed.y = 56.0f; + } + } + + if (dusk::getSettings().game.fastSpinner && mDoCPd_c::getHoldR(PAD_1)) { + if (const auto link = g_dComIfG_gameInfo.play.getPlayer(0)) { + auto spinnerActor = (fopAc_ac_c*)dynamic_cast(link)->getSpinnerActor(); + if (spinnerActor) { + if (spinnerActor->speedF < 60.f) + spinnerActor->speedF += 2.f; + } + } + } + + if (dusk::getSettings().game.infiniteHearts) { + dComIfGs_setLife((dComIfGs_getMaxLife() / 5) * 4); + } + + if (dusk::getSettings().game.infiniteArrows) { + dComIfGs_setArrowNum(dComIfGs_getArrowMax()); + } + + if (dusk::getSettings().game.infiniteBombs) { + dComIfGs_setBombNum(0, 99); + dComIfGs_setBombNum(1, 99); + dComIfGs_setBombNum(2, 99); + } + + if (dusk::getSettings().game.infiniteOil) { + dComIfGs_setOil(dComIfGs_getMaxOil()); + } + + if (dusk::getSettings().game.infiniteRupees) { + dComIfGs_setRupee(9999); + } + + if (dusk::getSettings().game.infiniteOxygen) { + dComIfGp_setOxygen(dComIfGp_getMaxOxygen()); + } +} #endif void fapGm_Execute() { @@ -748,31 +807,7 @@ void fapGm_Execute() { #endif #if TARGET_PC - if (const auto link = g_dComIfG_gameInfo.play.getPlayer(0)) { - dynamic_cast(link)->handleGamepadColor(); - } - - if (mDoCPd_c::getHoldR(PAD_1) && mDoCPd_c::getTrigX(PAD_1)) { - if (const auto link = g_dComIfG_gameInfo.play.getPlayer(0)) { - dynamic_cast(link)->handleWolfHowl(); - } - } - - if (mDoCPd_c::getHoldR(PAD_1) && mDoCPd_c::getTrigY(PAD_1)) { - if (const auto link = g_dComIfG_gameInfo.play.getPlayer(0)) { - dynamic_cast(link)->handleQuickTransform(); - } - } - - if (dusk::getSettings().game.fastSpinner && mDoCPd_c::getHoldR(PAD_1)) { - if (const auto link = g_dComIfG_gameInfo.play.getPlayer(0)) { - auto spinnerActor = (fopAc_ac_c*)dynamic_cast(link)->getSpinnerActor(); - if (spinnerActor) { - if (spinnerActor->speedF < 60.f) - spinnerActor->speedF += 2.f; - } - } - } + duskExecute(); #endif #ifdef TARGET_PC diff --git a/src/f_pc/f_pc_draw.cpp b/src/f_pc/f_pc_draw.cpp index 697d19cf29..8b775257fc 100644 --- a/src/f_pc/f_pc_draw.cpp +++ b/src/f_pc/f_pc_draw.cpp @@ -26,13 +26,7 @@ int fpcDw_Execute(base_process_class* i_proc) { } fpcLy_SetCurrentLayer(i_proc->layer_tag.layer); -#ifdef TARGET_PC - dusk::frame_interp::open_child(i_proc, 0); -#endif ret = draw_func(i_proc); -#ifdef TARGET_PC - dusk::frame_interp::close_child(); -#endif fpcLy_SetCurrentLayer(save_layer); return ret; } diff --git a/src/f_pc/f_pc_leaf.cpp b/src/f_pc/f_pc_leaf.cpp index eab6532a92..7f6d6df802 100644 --- a/src/f_pc/f_pc_leaf.cpp +++ b/src/f_pc/f_pc_leaf.cpp @@ -6,6 +6,10 @@ #include "f_pc/f_pc_leaf.h" #include "f_pc/f_pc_debug_sv.h" +#if TARGET_PC +#include "dusk/frame_interpolation.h" +#endif + s16 fpcLf_GetPriority(const leafdraw_class* i_leaf) { return fpcDwPi_Get(&i_leaf->draw_priority); } @@ -16,6 +20,11 @@ int fpcLf_DrawMethod(leafdraw_method_class* i_methods, void* i_process) { int fpcLf_Draw(leafdraw_class* i_leaf) { int ret = 0; +#if TARGET_PC + if (!i_leaf->draw_interp_frame && !dusk::frame_interp::is_sim_frame()) { + return ret; + } +#endif if (i_leaf->unk_0xBC == 0) { ret = fpcLf_DrawMethod(i_leaf->leaf_methods, i_leaf); } @@ -56,6 +65,9 @@ int fpcLf_Create(leafdraw_class* i_leaf) { LEAFDRAW_BASE(i_leaf).subtype = fpcBs_MakeOfType(&g_fpcLf_type); fpcDwPi_Init(&i_leaf->draw_priority, pprofile->priority); i_leaf->unk_0xBC = 0; +#if TARGET_PC + i_leaf->draw_interp_frame = false; +#endif } int ret = fpcMtd_Create(&i_leaf->leaf_methods->base, i_leaf); diff --git a/src/f_pc/f_pc_manager.cpp b/src/f_pc/f_pc_manager.cpp index 4d424ad75e..ad226b4d1d 100644 --- a/src/f_pc/f_pc_manager.cpp +++ b/src/f_pc/f_pc_manager.cpp @@ -65,7 +65,7 @@ void fpcM_Management(fpcM_ManagementFunc i_preExecuteFn, fpcM_ManagementFunc i_p #ifdef TARGET_PC // FRAME INTERP NOTE: Called in m_Do_main when interp is enabled - if (!dusk::getSettings().game.enableFrameInterpolation || dusk::getTransientSettings().skipFrameRateLimit) + if (!dusk::frame_interp::is_enabled()) #endif { cAPIGph_Painter(); diff --git a/src/f_pc/f_pc_node.cpp b/src/f_pc/f_pc_node.cpp index 205d392ff1..ca7bcc044b 100644 --- a/src/f_pc/f_pc_node.cpp +++ b/src/f_pc/f_pc_node.cpp @@ -7,6 +7,13 @@ #include "f_pc/f_pc_layer_iter.h" #include "f_pc/f_pc_debug_sv.h" +#if TARGET_PC +#include "f_op/f_op_draw_iter.h" +#include "f_pc/f_pc_manager.h" + +#include "dusk/frame_interpolation.h" +#endif + int fpcNd_DrawMethod(nodedraw_method_class* i_method_class, void* i_data) { return fpcMtd_Method(i_method_class->draw_method, i_data); } @@ -18,7 +25,17 @@ int fpcNd_Draw(process_node_class* i_procNode) { if (i_procNode->unk_0x1A8 == 0) { layer_class* save_layer = fpcLy_CurrentLayer(); fpcLy_SetCurrentLayer(&var_r28->layer); - ret = fpcNd_DrawMethod(i_procNode->nodedraw_method, i_procNode); +#if TARGET_PC + if (!i_procNode->draw_interp_frame && !dusk::frame_interp::is_sim_frame()) { + for (create_tag_class* i = fopDwIt_Begin(); i != NULL; i = fopDwIt_Next(i)) { + void* process = i->mpTagData; + fpcM_Draw(process); + } + } else +#endif + { + ret = fpcNd_DrawMethod(i_procNode->nodedraw_method, i_procNode); + } fpcLy_SetCurrentLayer(save_layer); } diff --git a/src/m_Do/m_Do_MemCard.cpp b/src/m_Do/m_Do_MemCard.cpp index 36de5480e5..a0c0302b09 100644 --- a/src/m_Do/m_Do_MemCard.cpp +++ b/src/m_Do/m_Do_MemCard.cpp @@ -84,11 +84,12 @@ void mDoMemCd_Ctrl_c::ThdInit() { mProbeStat = 2; mCardState = CARD_STATE_NO_CARD_e; - #if TARGET_PC - mCardState = CARD_STATE_READY_e; - #endif - +#if TARGET_PC + mCardCommand = COMM_ATTACH_e; +#else mCardCommand = COMM_NONE_e; +#endif + mChannel = SLOT_A; OSInitMutex(&mMutex); diff --git a/src/m_Do/m_Do_ext.cpp b/src/m_Do/m_Do_ext.cpp index 024dd596a8..fc8952e91d 100644 --- a/src/m_Do/m_Do_ext.cpp +++ b/src/m_Do/m_Do_ext.cpp @@ -25,6 +25,7 @@ #include #include #include "dusk/logging.h" +#include "dusk/frame_interpolation.h" u8 mDoExt::CurrentHeapAdjustVerbose; u8 mDoExt::HeapAdjustVerbose; @@ -349,6 +350,16 @@ void mDoExt_modelUpdateDL(J3DModel* i_model) { } void mDoExt_modelEntryDL(J3DModel* i_model) { +#if TARGET_PC + if (!dusk::frame_interp::is_sim_frame()) { + // FRAME INTERP NOTE: This fixes issue #355 where some lights would flicker. + // This is likely better solved by updating J3DMaterial::needsInterpCallBack, + // but it's unclear what exactly needs to be added. + i_model->diff(); + return; + } +#endif + modelMtxErrorCheck(i_model); J3DModelData* model_data = i_model->getModelData(); diff --git a/src/m_Do/m_Do_graphic.cpp b/src/m_Do/m_Do_graphic.cpp index 92c8707782..4dc505ac03 100644 --- a/src/m_Do/m_Do_graphic.cpp +++ b/src/m_Do/m_Do_graphic.cpp @@ -47,6 +47,8 @@ #endif #if TARGET_PC +#include +#include "aurora/lib/window.hpp" #include "d/actor/d_a_horse.h" #include "dusk/dusk.h" #include "dusk/endian.h" @@ -139,7 +141,7 @@ void dDlst_heapMap_c::draw() { f32 var_f29 = field_0x10 - field_0x8; f32 sp4C = field_0x14 - field_0xc; f32 sp48 = var_f29 * sp4C; - + uintptr_t start_addr = (uintptr_t)m_heap->getStartAddr(); uintptr_t end_addr = (uintptr_t)m_heap->getEndAddr(); u32 sp40 = end_addr - start_addr; @@ -154,7 +156,7 @@ void dDlst_heapMap_c::draw() { f32 var_f30 = (f32)sp38 * sp3C; uintptr_t sp34 = (uintptr_t)block - start_addr; f32 sp30 = (f32)sp34 * sp3C; - + f32 var_f28 = std::floor(sp30 / var_f29); f32 sp2C = sp30 - (var_f29 * var_f28); @@ -469,72 +471,32 @@ void darwFilter(GXColor matColor) { GXEnd(); } -#ifdef TARGET_PC -static void mDoGph_AdvanceFadeState() { - if (mDoGph_gInf_c::isFade() != 0) { - f32 fade_rate = mDoGph_gInf_c::getFadeRate() + mDoGph_gInf_c::getFadeSpeed(); - - if (fade_rate < 0.0f) { - fade_rate = 0.0f; - mDoGph_gInf_c::offFade(); - } else if (fade_rate > 1.0f) { - fade_rate = 1.0f; - } - - mDoGph_gInf_c::setFadeRate(fade_rate); - mDoGph_gInf_c::getFadeColor().a = 255.0f * fade_rate; - } else { - GXColor& fade_color = mDoGph_gInf_c::getFadeColor(); - if (dComIfG_getBrightness() != 255) { - fade_color.r = 0; - fade_color.g = 0; - fade_color.b = 0; - fade_color.a = 255 - dComIfG_getBrightness(); - } else { - fade_color.a = 0; - } - } -} - -static void mDoGph_AdvanceFadeState(u32 tick_count) { - for (u32 i = 0; i < tick_count; ++i) { - mDoGph_AdvanceFadeState(); - } -} - -static void mDoGph_DrawStoredFade() { - GXColor& fade_color = mDoGph_gInf_c::getFadeColor(); - if (fade_color.a != 0) { - darwFilter(fade_color); - } -} - void mDoGph_gInf_c::calcFade() { - mDoGph_AdvanceFadeState(); - mDoGph_DrawStoredFade(); -} -#else -void mDoGph_gInf_c::calcFade() { - if (mDoGph_gInf_c::mFade != 0) { - mFadeRate += mFadeSpeed; +#if TARGET_PC + if (dusk::frame_interp::get_ui_tick_pending()) +#endif + { + if (mFade != 0) { + mFadeRate += mFadeSpeed; - if (mFadeRate < 0.0f) { - mFadeRate = 0.0f; - mDoGph_gInf_c::mFade = 0; - } else { - if (mFadeRate > 1.0f) { - mFadeRate = 1.0f; + if (mFadeRate < 0.0f) { + mFadeRate = 0.0f; + mFade = 0; + } else { + if (mFadeRate > 1.0f) { + mFadeRate = 1.0f; + } } - } - mFadeColor.a = 255.0f * mFadeRate; - } else { - if (dComIfG_getBrightness() != 255) { - mFadeColor.r = 0; - mFadeColor.g = 0; - mFadeColor.b = 0; - mFadeColor.a = 255 - dComIfG_getBrightness(); + mFadeColor.a = 255.0f * mFadeRate; } else { - mFadeColor.a = 0; + if (dComIfG_getBrightness() != 255) { + mFadeColor.r = 0; + mFadeColor.g = 0; + mFadeColor.b = 0; + mFadeColor.a = 255 - dComIfG_getBrightness(); + } else { + mFadeColor.a = 0; + } } } @@ -542,7 +504,6 @@ void mDoGph_gInf_c::calcFade() { darwFilter(mFadeColor); } } -#endif #if PLATFORM_WII || PLATFORM_SHIELD u32 mDoGph_gInf_c::csr_c::m_blurID; @@ -601,21 +562,16 @@ struct tvSize { u16 width; u16 height; }; -const tvSize l_tvSize[2] = { +#ifndef TARGET_PC +const +#endif +tvSize l_tvSize[2] = { {FB_WIDTH_BASE, FB_HEIGHT_BASE}, {808, FB_HEIGHT_BASE}, }; -#if TARGET_PC -tvSize pc_tvSize = {608, 448}; -#endif - void mDoGph_gInf_c::setTvSize() { -#if TARGET_PC - const tvSize* tvsize = &pc_tvSize; -#else const tvSize* tvsize = &l_tvSize[mWide]; -#endif m_width = tvsize->width; m_height = tvsize->height; @@ -636,26 +592,17 @@ void mDoGph_gInf_c::setTvSize() { m_invScale = 1.0f / m_scale; #if TARGET_PC - hudAspectScaleDown = 1.3571428f / mDoGph_gInf_c::getAspect(); - hudAspectScaleUp = 1.0f / hudAspectScaleDown; + updateSafeAreaBounds(); + hudAspectScaleUp = getSafeWidthF() / FB_WIDTH_BASE; + hudAspectScaleDown = FB_WIDTH_BASE / getSafeWidthF(); #endif } -#if TARGET_PC -void mDoGph_gInf_c::onWide(f32 width, f32 height) { - mWide = TRUE; - pc_tvSize.width = width; - pc_tvSize.height = height; - setTvSize(); - dMeter2Info_onWide2D(); -} -#else void mDoGph_gInf_c::onWide() { mWide = TRUE; setTvSize(); dMeter2Info_onWide2D(); } -#endif void mDoGph_gInf_c::offWide() { mWide = FALSE; @@ -765,14 +712,101 @@ void mDoGph_gInf_c::setWideZoomLightProjection(Mtx& m) { #if TARGET_PC f32 mDoGph_gInf_c::hudAspectScaleDown = 1.0f; f32 mDoGph_gInf_c::hudAspectScaleUp = 1.0f; +f32 mDoGph_gInf_c::m_safeMinXF = 0.0f; +f32 mDoGph_gInf_c::m_safeMinYF = 0.0f; +f32 mDoGph_gInf_c::m_safeMaxXF = FB_WIDTH_BASE; +f32 mDoGph_gInf_c::m_safeMaxYF = FB_HEIGHT_BASE; +f32 mDoGph_gInf_c::m_safeWidthF = FB_WIDTH_BASE; +f32 mDoGph_gInf_c::m_safeHeightF = FB_HEIGHT_BASE; -void mDoGph_gInf_c::setWindowSize(AuroraWindowSize const& size) { - JUTVideo::getManager()->setWindowSize(size); - dComIfGp_setWindow(0, 0.0f, 0.0f, getWidth(), getHeight(), 0.0f, 1.0f, 0, 2); - mFader->mBox.set(0, 0, getWidth(), getHeight()); +void mDoGph_gInf_c::updateSafeAreaBounds() { + m_safeMinXF = m_minXF; + m_safeMinYF = m_minYF; + m_safeMaxXF = m_maxXF; + m_safeMaxYF = m_maxYF; + m_safeWidthF = m_widthF; + m_safeHeightF = m_heightF; - f32 newWidth = (getWidth() / getHeight()) * 448.0f; - onWide(newWidth, 448.0f); + SDL_Window* window = aurora::window::get_sdl_window(); + if (window == NULL) { + return; + } + + const AuroraWindowSize windowSize = aurora::window::get_window_size(); + const f32 windowWidth = static_cast(windowSize.width); + const f32 windowHeight = static_cast(windowSize.height); + if (windowWidth <= 0.0f || windowHeight <= 0.0f) { + return; + } + + SDL_Rect safeRect{}; + if (!SDL_GetWindowSafeArea(window, &safeRect)) { + return; + } + + u32 renderWidth = 0; + u32 renderHeight = 0; + AuroraGetRenderSize(&renderWidth, &renderHeight); + if (windowSize.native_fb_width == 0 || windowSize.native_fb_height == 0 || + renderWidth == 0 || renderHeight == 0) + { + return; + } + + const f32 nativeScaleX = static_cast(windowSize.native_fb_width) / windowWidth; + const f32 nativeScaleY = static_cast(windowSize.native_fb_height) / windowHeight; + + const f32 safeLeft = static_cast(safeRect.x) * nativeScaleX; + const f32 safeTop = static_cast(safeRect.y) * nativeScaleY; + const f32 safeRight = static_cast(safeRect.x + safeRect.w) * nativeScaleX; + const f32 safeBottom = static_cast(safeRect.y + safeRect.h) * nativeScaleY; + + f32 viewportWidth = static_cast(windowSize.native_fb_width); + f32 viewportHeight = static_cast(windowSize.native_fb_height); + const f32 targetAspect = viewportWidth / viewportHeight; + const f32 contentAspect = static_cast(renderWidth) / static_cast(renderHeight); + if (targetAspect > contentAspect) { + viewportWidth = std::max(1.0f, std::round(viewportHeight * contentAspect)); + } else if (targetAspect < contentAspect) { + viewportHeight = std::max(1.0f, std::round(viewportWidth / contentAspect)); + } + + const f32 viewportLeft = (static_cast(windowSize.native_fb_width) - viewportWidth) * 0.5f; + const f32 viewportTop = (static_cast(windowSize.native_fb_height) - viewportHeight) * 0.5f; + const f32 viewportRight = viewportLeft + viewportWidth; + const f32 viewportBottom = viewportTop + viewportHeight; + + const f32 leftInset = std::max(0.0f, safeLeft - viewportLeft) * (m_widthF / viewportWidth); + const f32 topInset = std::max(0.0f, safeTop - viewportTop) * (m_heightF / viewportHeight); + const f32 rightInset = std::max(0.0f, viewportRight - safeRight) * (m_widthF / viewportWidth); + const f32 bottomInset = std::max(0.0f, viewportBottom - safeBottom) * (m_heightF / viewportHeight); + + const f32 safeMinXF = m_minXF + leftInset; + const f32 safeMinYF = m_minYF + topInset; + const f32 safeMaxXF = m_maxXF - rightInset; + const f32 safeMaxYF = m_maxYF - bottomInset; + const f32 safeWidthF = safeMaxXF - safeMinXF; + const f32 safeHeightF = safeMaxYF - safeMinYF; + + if (safeWidthF <= 0.0f || safeHeightF <= 0.0f) { + return; + } + + m_safeMinXF = safeMinXF; + m_safeMinYF = safeMinYF; + m_safeMaxXF = safeMaxXF; + m_safeMaxYF = safeMaxYF; + m_safeWidthF = safeWidthF; + m_safeHeightF = safeHeightF; +} + +void mDoGph_gInf_c::updateRenderSize() { + u32 width, height; + AuroraGetRenderSize(&width, &height); + JUTVideo::getManager()->setRenderSize(width, height); + l_tvSize[1].width = static_cast(static_cast(width) / static_cast(height) * + static_cast(l_tvSize[1].height)); + onWide(); } #endif @@ -869,9 +903,6 @@ int mDoGph_AfterOfDraw() { JUTVideo::getManager()->setRenderMode(mDoMch_render_c::getRenderModeObj()); mDoGph_gInf_c::endFrame(); -#ifdef TARGET_PC - dusk::frame_interp::notify_sim_tick_complete(); -#endif return 1; } @@ -911,7 +942,7 @@ static void drawDepth2(view_class* param_0, view_port_class* param_1, int param_ GXProject(param_0->lookat.center.x, param_0->lookat.center.y, param_0->lookat.center.z, param_0->viewMtx, sp4C, sp34, &sp1C, &sp18, &sp14); - + param_2 = (0xFF0000 - (int)(16777215.0f * sp14)) >> 8; param_2 = cLib_minMaxLimit(param_2, -0x400, 0); } @@ -1124,6 +1155,9 @@ static void drawDepth2(view_class* param_0, view_port_class* param_1, int param_ GXSetProjection(ortho, GX_ORTHOGRAPHIC); GXSetCurrentMtx(0); +#ifdef TARGET_PC + if (dusk::getSettings().game.enableDepthOfField) +#endif if (l_tevColor0.a > -255 && sp8 == 1) { GXBegin(GX_QUADS, GX_VTXFMT0, 4); GXPosition3s16(x_orig, y_orig_pos, -5); @@ -1276,8 +1310,8 @@ void mDoGph_gInf_c::bloom_c::draw2() { if (mMonoColor.a == 0 && !enabled) return; - f32 width = mDoGph_gInf_c::getWidth(); - f32 height = mDoGph_gInf_c::getHeight(); + f32 width = JUTVideo::getManager()->getRenderWidth(); + f32 height = JUTVideo::getManager()->getRenderHeight(); GXLoadTexObj(getFrameBufferTexObj(), GX_TEXMAP0); GXSetNumChans(0); @@ -1344,6 +1378,11 @@ void mDoGph_gInf_c::bloom_c::draw2() { static_cast(prev.h / 2), }; } + for (int i = 0; i < ARRAY_SIZE(divRects); i++) { + auto & rect = divRects[i]; + if (rect.w == 0) rect.w = 1; + if (rect.h == 0) rect.h = 1; + } auto divCopySrc = [&](int divNo) { auto const& rect = divRects[divNo]; @@ -1356,7 +1395,7 @@ void mDoGph_gInf_c::bloom_c::draw2() { CopyToTexObj(&tmpTex[texNo], texNo, rect.w, rect.h); return &tmpTex[texNo]; }; - + auto divQuad = [&](int divNo) { auto const& rect = divRects[divNo]; f32 x0 = rect.x / width; @@ -1389,8 +1428,8 @@ void mDoGph_gInf_c::bloom_c::draw2() { } if (enabled) { - GXCreateFrameBuffer(width * 0.75f, height * 0.5f); - GXSetViewport(0.0f, 0.0f, width, height, 0.1f, 1.0f); // use oversized viewport to make the math easier + GXCreateFrameBuffer(divRects[2].x + divRects[2].w, divRects[1].y + divRects[1].h); + GXSetViewportRender(0.0f, 0.0f, width, height, 0.0f, 1.0f); // use oversized viewport to make the math easier GXSetNumTevStages(3); GXSetTevOrder(GX_TEVSTAGE0, GX_TEXCOORD0, GX_TEXMAP0, GX_COLOR_NULL); @@ -1427,21 +1466,21 @@ void mDoGph_gInf_c::bloom_c::draw2() { GXTexObj* texPass0 = divCopyTex(Pass0, 2); GXLoadTexObj(texPass0, GX_TEXMAP0); - f32 blurScale = mBlureSize * ((448.0f / getHeight()) / 6400.0f); + f32 blurScale = mBlureSize * ((448.0f / height) / 6400.0f); // Setup blur filter TEV. GXSetNumTexGens(8); - u32 texMtxID = GX_TEXMTX0; - int angle = 0; - for (int texCoord = (int)GX_TEXCOORD0; texCoord < (int)GX_MAX_TEXCOORD; texCoord++) { - GXSetTexCoordGen((GXTexCoordID)texCoord, GX_TG_MTX2x4, GX_TG_TEX0, texMtxID); - mDoMtx_stack_c::transS((blurScale * cM_scos(angle)) * getInvScale(), - blurScale * cM_ssin(angle), 0.0f); - GXLoadTexMtxImm(mDoMtx_stack_c::get(), texMtxID, GX_MTX2x4); - texMtxID += 3; - angle += 0x2000; - } + u32 texMtxID = GX_TEXMTX0; + int angle = 0; + for (int texCoord = (int)GX_TEXCOORD0; texCoord < (int)GX_MAX_TEXCOORD; texCoord++) { + GXSetTexCoordGen((GXTexCoordID)texCoord, GX_TG_MTX2x4, GX_TG_TEX0, texMtxID); + mDoMtx_stack_c::transS((blurScale * cM_scos(angle)) * getInvScale(), + blurScale * cM_ssin(angle), 0.0f); + GXLoadTexMtxImm(mDoMtx_stack_c::get(), texMtxID, GX_MTX2x4); + texMtxID += 3; + angle += 0x2000; + } GXSetNumTevStages(8); for (int stage = 0; stage < 8; stage++) { @@ -1461,7 +1500,7 @@ void mDoGph_gInf_c::bloom_c::draw2() { // This is applied over two passes, the second one with an alpha of 25%; however, the clipping that this introduces is a bit integral to the look, // so we do the same thing, letting it clip. float brightnessF32 = (mBlureRatio * 16 / 255.0f); - + // Distribute the brightness through the total number of passes. f32 totalNumPasses = (divNum - divStart + 1); float brightnessPerPass = 255.0f * powf(brightnessF32, 1.0f / totalNumPasses); @@ -1488,7 +1527,7 @@ void mDoGph_gInf_c::bloom_c::draw2() { GXSetTexCoordGen(GX_TEXCOORD0, GX_TG_MTX2x4, GX_TG_TEX0, GX_IDENTITY); GXSetBlendMode(GX_BM_BLEND, GX_BL_SRCALPHA, GX_BL_ONE, GX_LO_OR); for (int i = divNum; i > divStart; i--) { - float alpha = 255.0f * powf(0.25f * dusk::getSettings().game.bloomMultiplier.getValue(), 1.0f / (divNum - i + 1)); + float alpha = 255.0f * powf(0.25f * dusk::getSettings().game.bloomMultiplier.getValue(), 1.0f / (i - divStart + 1)); GXSetTevColorS10(GX_TEVREG0, {0, 0, 0, s16(alpha)}); divCopySrc(i); @@ -1503,7 +1542,6 @@ void mDoGph_gInf_c::bloom_c::draw2() { GXLoadTexObj(texFinal, GX_TEXMAP0); GXRestoreFrameBuffer(); - GXSetViewport(0.0f, 0.0f, width, height, 0.0f, 1.0f); // Now blend our bloom into the real FB. GXSetTevColor(GX_TEVREG0, mBlendColor); @@ -1534,13 +1572,8 @@ void mDoGph_gInf_c::bloom_c::draw() { bool enabled = mEnable && m_buffer != NULL; if (mMonoColor.a != 0 || enabled) { -#if TARGET_PC - f32 width = mDoGph_gInf_c::getWidth(); - f32 height = mDoGph_gInf_c::getHeight(); -#else f32 width = FB_WIDTH; f32 height = FB_HEIGHT; -#endif GXSetViewport(0.0f, 0.0f, width, height, 0.0f, 1.0f); GXSetScissor(0, 0, width, height); @@ -1655,7 +1688,7 @@ void mDoGph_gInf_c::bloom_c::draw() { GXSetTexCoordGen(GX_TEXCOORD0, GX_TG_MTX2x4, GX_TG_TEX0, GX_IDENTITY); for (int texCoord = (int)GX_TEXCOORD1; texCoord < (int)GX_MAX_TEXCOORD; texCoord++) { GXSetTexCoordGen((GXTexCoordID)texCoord, GX_TG_MTX2x4, GX_TG_TEX0, texMtxID); - + #if TARGET_PC f32 dVar15 = mBlureSize * ((448.0f / height) / 6400.0f); #else @@ -1796,7 +1829,7 @@ static void retry_captue_frame(view_class* param_0, view_port_class* param_1, in var_r23 = height >> 1; GXSetTexCopySrc(x_orig, y_orig_pos, width, height); #ifdef TARGET_PC - GXSetTexCopyDst(width, height, (GXTexFmt)mDoGph_gInf_c::getFrameBufferTimg()->format, GX_TRUE); + GXSetTexCopyDst(width, height, (GXTexFmt)mDoGph_gInf_c::getFrameBufferTimg()->format, GX_FALSE); #else GXSetTexCopyDst(var_r24, var_r23, (GXTexFmt)mDoGph_gInf_c::getFrameBufferTimg()->format, GX_TRUE); #endif @@ -2025,8 +2058,6 @@ int mDoGph_Painter() { #if TARGET_PC dusk::g_imguiConsole.PreDraw(); - - const u32 pending_ui_ticks = dusk::frame_interp::begin_presentation_ui_pass(); #endif #if DEBUG @@ -2034,7 +2065,7 @@ int mDoGph_Painter() { #endif #ifdef TARGET_PC - for (u32 i = 0; i < pending_ui_ticks; ++i) + if (dusk::frame_interp::get_ui_tick_pending()) #endif { dComIfGp_particle_calcMenu(); @@ -2054,8 +2085,7 @@ int mDoGph_Painter() { j3dSys.drawInit(); GXSetDither(GX_ENABLE); - J2DOrthoGraph ortho(0.0f, 0.0f, mDoGph_gInf_c::getWidth(), mDoGph_gInf_c::getHeight(), -1.0f, - 1.0f); + J2DOrthoGraph ortho(0.0f, 0.0f, FB_WIDTH, FB_HEIGHT, -1.0f, 1.0f); ortho.setOrtho(mDoGph_gInf_c::getMinXF(), mDoGph_gInf_c::getMinYF(), mDoGph_gInf_c::getWidthF(), mDoGph_gInf_c::getHeightF(), -1.0f, 1.0f); @@ -2101,13 +2131,8 @@ int mDoGph_Painter() { view_port_class new_port; new_port.x_orig = 0.0f; new_port.y_orig = 0.0f; - #if TARGET_PC - new_port.width = mDoGph_gInf_c::getWidth(); - new_port.height = mDoGph_gInf_c::getHeight(); - #else new_port.width = FB_WIDTH; new_port.height = FB_HEIGHT; - #endif new_port.near_z = view_port->near_z; new_port.far_z = view_port->far_z; new_port.scissor = view_port->scissor; @@ -2125,7 +2150,7 @@ int mDoGph_Painter() { view_port->height); #ifdef TARGET_PC - // Frame interpolation: Call setViewMtx earlier so that it's interpolated in time for draw_info to use it + // FRAME INTERP NOTE: Call setViewMtx earlier so that it's interpolated in time for draw_info to use it j3dSys.setViewMtx(camera_p->view.viewMtx); JPADrawInfo draw_info(j3dSys.getViewMtx(), camera_p->view.fovy, camera_p->view.aspect); #else @@ -2161,7 +2186,7 @@ int mDoGph_Painter() { dComIfGp_setCurrentView(&camera_p->view); dComIfGp_setCurrentViewport(view_port); GXSetProjection(camera_p->view.projMtx, GX_PERSPECTIVE); - + #if DEBUG captureScreenSetProjection(camera_p->view.projMtx); #endif @@ -2229,18 +2254,16 @@ int mDoGph_Painter() { #if TARGET_PC if (dusk::getSettings().game.enableFrameInterpolation) { - cXyz pres_eye; - dusk::frame_interp::camera_eye_from_view_mtx(j3dSys.getViewMtx(), &pres_eye); // FRAME INTERP NOTE: Currently only recalculating points for Epona's reins. Need a more global solution. if (daHorse_c* horse = dComIfGp_getHorseActor()) { horse->lerpControlPoints(dusk::frame_interp::get_interpolation_step()); } - g_dComIfG_gameInfo.drawlist.refresh3DlineMats(pres_eye); + g_dComIfG_gameInfo.drawlist.refresh3DlineMats(camera_p->view.lookat.eye); } #endif GX_DEBUG_GROUP(dComIfGd_drawOpaListPacket); - + #if DEBUG // "drawing up to special-use drawing (Opaque) except J3D (Rendering)" fapGm_HIO_c::stopCpuTimer("J3D以外などの特殊用(不透明)描画まで(レンダリング)"); @@ -2322,7 +2345,7 @@ int mDoGph_Painter() { GX_DEBUG_GROUP(dComIfGd_drawXluListInvisible); } } - + #if DEBUG // "drawing up to projection (Translucent)" @@ -2426,12 +2449,8 @@ int mDoGph_Painter() { retry_captue_frame(&camera_p->view, view_port, dComIfGp_getCameraZoomForcus(camera_id)); } - - #if TARGET_PC - GXSetViewport(0.0f, 0.0f, mDoGph_gInf_c::getWidth(), mDoGph_gInf_c::getHeight(), 0.0f, 1.0f); - #else + GXSetViewport(0.0f, 0.0f, FB_WIDTH, FB_HEIGHT, 0.0f, 1.0f); - #endif Mtx m2; Mtx44 m; @@ -2528,12 +2547,7 @@ int mDoGph_Painter() { if (strcmp(dComIfGp_getStartStageName(), "F_SP127") != 0 && (mDoGph_gInf_c::isFade() & 0x80) == 0) { -#ifdef TARGET_PC - mDoGph_AdvanceFadeState(pending_ui_ticks); - mDoGph_DrawStoredFade(); -#else mDoGph_gInf_c::calcFade(); -#endif } #if DEBUG @@ -2599,7 +2613,7 @@ int mDoGph_Painter() { GXSetClipMode(GX_CLIP_ENABLE); #if TARGET_PC - for (u32 i = 0; i < pending_ui_ticks; ++i) + if (dusk::frame_interp::get_ui_tick_pending()) #endif { dDlst_list_c::calcWipe(); @@ -2653,12 +2667,7 @@ int mDoGph_Painter() { if (strcmp(dComIfGp_getStartStageName(), "F_SP127") == 0 || (mDoGph_gInf_c::isFade() & 0x80) != 0) { -#ifdef TARGET_PC - mDoGph_AdvanceFadeState(pending_ui_ticks); - mDoGph_DrawStoredFade(); -#else mDoGph_gInf_c::calcFade(); -#endif } GX_DEBUG_GROUP(dComIfGp_particle_draw2DmenuFore, &draw_info3); @@ -2691,18 +2700,10 @@ int mDoGph_Painter() { #if TARGET_PC dusk::g_imguiConsole.PostDraw(); - - if (dusk::getSettings().game.enableFrameInterpolation) { - JFWDisplay::getManager()->setFaderSimSteps(pending_ui_ticks); - } #endif mDoGph_gInf_c::endRender(); -#ifdef TARGET_PC - dusk::frame_interp::end_presentation_ui_pass(); -#endif - #if WIDESCREEN_SUPPORT mDoGph_gInf_c::offWideZoom(); #endif diff --git a/src/m_Do/m_Do_lib.cpp b/src/m_Do/m_Do_lib.cpp index 93df5c23cc..921681d464 100644 --- a/src/m_Do/m_Do_lib.cpp +++ b/src/m_Do/m_Do_lib.cpp @@ -95,8 +95,13 @@ void mDoLib_project(Vec* src, Vec* dst) { xOffset = (0.5f * ((2.0f * viewPort->x_orig) + viewPort->width)) - (int)(FB_WIDTH / 2); xSize = FB_WIDTH; } else { +#if TARGET_PC + xOffset = mDoGph_gInf_c::getSafeMinXF(); + xSize = viewPort->width * mDoGph_gInf_c::hudAspectScaleUp; +#else xOffset = viewPort->x_orig; xSize = viewPort->width; +#endif } if (viewPort->y_orig != 0.0f) { @@ -108,73 +113,15 @@ void mDoLib_project(Vec* src, Vec* dst) { } dst->x = ((0.5f + (multVec.x * calcFloat)) * xSize) + xOffset; + #if TARGET_PC + if (dusk::getSettings().game.enableMirrorMode) { + dst->x = ((0.5f + (-multVec.x * calcFloat)) * xSize) + xOffset; + } + #endif + dst->y = ((0.5f + (multVec.y * (-calcFloat))) * ySize) + yOffset; } - -#if TARGET_PC -void mDoLib_project(Vec* src, Vec* dst, JGeometry::TBox2 viewport) { - if (dComIfGd_getView() == NULL) { - dst->x = 0.0f; - dst->y = 0.0f; - dst->z = 0.0f; - return; - } - - { int unused; } - - Vec multVec; - cMtx_multVec(*dComIfGd_getProjViewMtx(), src, &multVec); - - f32 calcFloat = (src->x * (*dComIfGd_getProjViewMtx())[3][0]) + - (src->y * (*dComIfGd_getProjViewMtx())[3][1]) + - (src->z * (*dComIfGd_getProjViewMtx())[3][2]) + - (*dComIfGd_getProjViewMtx())[3][3]; - if (multVec.z >= 0.0f) { - multVec.z = 0.0f; - } - if (calcFloat <= 0.0f) { - if (calcFloat == 0.0f) { - dst->z = multVec.z * 500000.0f; - } else { - dst->z = multVec.z * (0.5f / calcFloat); - } - calcFloat = 500000.0f; - } else { - calcFloat = 0.5f / calcFloat; - dst->z = multVec.z * calcFloat; - } - - f32 xOffset; - f32 yOffset; - f32 xSize; - f32 ySize; - if (viewport.i.x != 0.0f) { - xOffset = (0.5f * ((2.0f * viewport.i.x) + viewport.f.x)) - (int)(FB_WIDTH / 2); - xSize = FB_WIDTH; - } else { - #if TARGET_PC - xOffset = mDoGph_gInf_c::getMinXF(); - xSize = viewport.f.x * mDoGph_gInf_c::hudAspectScaleUp; - #else - xOffset = viewport.i.x; - xSize = viewport.f.x; - #endif - } - - if (viewport.i.y != 0.0f) { - yOffset = (0.5f * ((2.0f * viewport.i.y) + viewport.f.y)) - (int)(FB_HEIGHT / 2); - ySize = FB_HEIGHT; - } else { - yOffset = viewport.i.y; - ySize = viewport.f.y; - } - - dst->x = ((0.5f + (multVec.x * calcFloat)) * xSize) + xOffset; - dst->y = ((0.5f + (multVec.y * (-calcFloat))) * ySize) + yOffset; -} -#endif - void mDoLib_pos2camera(Vec* src, Vec* dst) { if (dComIfGd_getView() == NULL) { dst->x = 0.0f; diff --git a/src/m_Do/m_Do_main.cpp b/src/m_Do/m_Do_main.cpp index 3568e044e1..579cbd8c8b 100644 --- a/src/m_Do/m_Do_main.cpp +++ b/src/m_Do/m_Do_main.cpp @@ -42,7 +42,6 @@ #include "SSystem/SComponent/c_counter.h" #include -#include #include #include #include @@ -51,7 +50,8 @@ #include "dusk/crash_reporting.h" #include "dusk/dusk.h" #include "dusk/frame_interpolation.h" -#include "dusk/gyro_aim.h" +#include "dusk/game_clock.h" +#include "dusk/gyro.h" #include "dusk/imgui/ImGuiEngine.hpp" #include "dusk/logging.h" #include "dusk/main.h" @@ -66,9 +66,14 @@ #include "SDL3/SDL_filesystem.h" #include "cxxopts.hpp" +#include "d/actor/d_a_movie_player.h" #include "dusk/audio/DuskAudioSystem.h" #include "dusk/config.hpp" #include "dusk/imgui/ImGuiConsole.hpp" +#include "dusk/settings.h" +#include "dusk/discord_presence.hpp" +#include "tracy/Tracy.hpp" +#include "f_pc/f_pc_draw.h" #include "tracy/Tracy.hpp" // --- GLOBALS --- @@ -92,6 +97,8 @@ const int audioHeapSize = 0x14D800; bool dusk::IsRunning = true; bool dusk::IsShuttingDown = false; bool dusk::IsGameLaunched = false; +bool dusk::IsFocusPaused = false; +std::filesystem::path dusk::ConfigPath; #endif s32 LOAD_COPYDATE(void*) { @@ -124,17 +131,14 @@ s32 LOAD_COPYDATE(void*) { AuroraInfo auroraInfo; AuroraStats dusk::lastFrameAuroraStats; float dusk::frameUsagePct = 0.0f; -const char* configPath; - -AuroraWindowSize preLaunchUIWindowSize; bool launchUILoop() { while (dusk::IsRunning && !dusk::IsGameLaunched) { const AuroraEvent* event = aurora_update(); while (event != nullptr && event->type != AURORA_NONE) { switch (event->type) { - case AURORA_WINDOW_RESIZED: - preLaunchUIWindowSize = event->windowSize; + case AURORA_SDL_EVENT: + dusk::g_imguiConsole.HandleSDLEvent(event->sdl); break; case AURORA_DISPLAY_SCALE_CHANGED: dusk::ImGuiEngine_Initialize(event->windowSize.scale); @@ -199,12 +203,7 @@ void main01(void) { OSReport("Entering Main Loop (main01)...\n"); - if (preLaunchUIWindowSize.width != 0) - mDoGph_gInf_c::setWindowSize(preLaunchUIWindowSize); - - constexpr double kSimStepSeconds = 1.0 / 30.0; - auto previous_time = std::chrono::steady_clock::now(); - double accumulator = kSimStepSeconds; + dusk::game_clock::ensure_initialized(); do { // 1. Update Window Events @@ -213,8 +212,18 @@ void main01(void) { switch (event->type) { case AURORA_NONE: goto eventsDone; - case AURORA_WINDOW_RESIZED: - mDoGph_gInf_c::setWindowSize(event->windowSize); + case AURORA_SDL_EVENT: + dusk::g_imguiConsole.HandleSDLEvent(event->sdl); + if (event->sdl.type == SDL_EVENT_WINDOW_FOCUS_LOST && + dusk::getSettings().game.pauseOnFocusLost) { + dusk::IsFocusPaused = true; + dusk::audio::SetPaused(true); + } else if (event->sdl.type == SDL_EVENT_WINDOW_FOCUS_GAINED && + dusk::IsFocusPaused) { + dusk::IsFocusPaused = false; + dusk::audio::SetPaused(false); + dusk::game_clock::reset_frame_timer(); + } break; case AURORA_DISPLAY_SCALE_CHANGED: dusk::ImGuiEngine_Initialize(event->windowSize.scale); @@ -228,10 +237,10 @@ void main01(void) { eventsDone:; - auto current_time = std::chrono::steady_clock::now(); - double frame_seconds = std::chrono::duration(current_time - previous_time).count(); - previous_time = current_time; - accumulator += frame_seconds; + if (dusk::IsFocusPaused) { + std::this_thread::sleep_for(std::chrono::milliseconds(16)); + continue; + } VIWaitForRetrace(); @@ -241,27 +250,40 @@ void main01(void) { continue; } - if (dusk::getSettings().game.enableFrameInterpolation && !dusk::getTransientSettings().skipFrameRateLimit) { - dusk::frame_interp::notify_presentation_frame(); - while (accumulator >= kSimStepSeconds) { - mDoCPd_c::read(); - if (dusk::getSettings().game.enableGyroAim) { - dusk::gyro_aim::read(static_cast(kSimStepSeconds), dusk::gyro_aim::queryGyroAimItemContext()); + mDoGph_gInf_c::updateRenderSize(); + + const auto pacing = dusk::game_clock::advance_main_loop(); + if (pacing.is_interpolating) { + if (pacing.sim_ticks_to_run > 0) { + dusk::frame_interp::begin_frame(true, true, 0.0f); + dusk::frame_interp::set_ui_tick_pending(true); + for (int sim_tick = 0; sim_tick < pacing.sim_ticks_to_run; ++sim_tick) { + dusk::frame_interp::begin_sim_tick(); + mDoCPd_c::read(); + DuskDebugPad(); + dusk::gyro::read(pacing.sim_pace); + fapGm_Execute(); + mDoAud_Execute(); + dusk::game_clock::commit_sim_tick(); } - fapGm_Execute(); - mDoAud_Execute(); - accumulator -= kSimStepSeconds; } - dusk::frame_interp::interpolate(static_cast(accumulator / kSimStepSeconds)); + + dusk::frame_interp::begin_frame(true, false, + dusk::game_clock::sample_interpolation_step()); + dusk::frame_interp::interpolate(); + dusk::frame_interp::begin_presentation_camera(); + // run draw functions for anything specially marked to handle interp + fpcM_DrawIterater((fpcM_DrawIteraterFunc)fpcM_Draw); cAPIGph_Painter(); + dusk::frame_interp::end_presentation_camera(); + dusk::frame_interp::set_ui_tick_pending(false); } else { - accumulator = 0.0; - + dusk::frame_interp::begin_frame(false, true, 0.0f); + dusk::frame_interp::set_ui_tick_pending(true); + // Game Inputs mDoCPd_c::read(); - if (dusk::getSettings().game.enableGyroAim) { - dusk::gyro_aim::read(static_cast(frame_seconds), dusk::gyro_aim::queryGyroAimItemContext()); - } + dusk::gyro::read(pacing.presentation_dt_seconds); // EXECUTE GAME LOGIC & RENDER // This calls mDoGph_Painter -> JFWDisplay -> GX Functions @@ -273,6 +295,11 @@ void main01(void) { aurora_end_frame(); FrameMark; + +#ifdef DUSK_DISCORD_RPC + dusk::discord::RunCallbacks(); + dusk::discord::UpdatePresence(); +#endif } while (dusk::IsRunning); exit:; @@ -355,7 +382,7 @@ static void ApplyCVarOverrides(const cxxopts::OptionValue& option) { } } -static const char* CalculateConfigPath() { +static std::filesystem::path CalculateConfigPath() { const auto result = SDL_GetPrefPath(dusk::OrgName, dusk::AppName); if (!result) { DuskLog.fatal("Unable to get PrefPath: {}", SDL_GetError()); @@ -364,13 +391,12 @@ static const char* CalculateConfigPath() { return result; } -static void EnsureInitialPipelineCache(const char* configDir) { - if (configDir == nullptr) { +static void EnsureInitialPipelineCache(const std::filesystem::path& configDir) { + if (configDir.empty()) { return; } - const std::filesystem::path configPathFs(configDir); - const std::filesystem::path pipelineCachePath = configPathFs / "pipeline_cache.db"; + const std::filesystem::path pipelineCachePath = configDir / "pipeline_cache.db"; if (std::filesystem::exists(pipelineCachePath)) { return; } @@ -389,10 +415,10 @@ static void EnsureInitialPipelineCache(const char* configDir) { } std::error_code ec; - std::filesystem::create_directories(configPathFs, ec); + std::filesystem::create_directories(configDir, ec); if (ec) { DuskLog.warn("Failed to create config directory '{}' for pipeline cache: {}", - configPathFs.string(), ec.message()); + configDir.string(), ec.message()); return; } @@ -483,47 +509,53 @@ int game_main(int argc, char* argv[]) { exit(1); } - configPath = CalculateConfigPath(); + dusk::ConfigPath = CalculateConfigPath(); const auto startupLogLevel = static_cast(parsed_arg_options["log-level"].as()); - dusk::InitializeFileLogging(configPath, startupLogLevel); + dusk::InitializeFileLogging(dusk::ConfigPath, startupLogLevel); dusk::config::LoadFromUserPreferences(); ApplyCVarOverrides(parsed_arg_options["cvar"]); dusk::InitializeCrashReporting(); - EnsureInitialPipelineCache(configPath); - - AuroraConfig config{}; - config.appName = dusk::AppName; - config.configPath = configPath; - config.vsync = dusk::getSettings().video.enableVsync; - config.startFullscreen = dusk::getSettings().video.enableFullscreen; - config.windowPosX = -1; - config.windowPosY = -1; - config.windowWidth = defaultWindowWidth * 2; - config.windowHeight = defaultWindowHeight * 2; - config.desiredBackend = ResolveDesiredBackend(parsed_arg_options); - config.logCallback = &aurora_log_callback; - config.logLevel = startupLogLevel; - config.mem1Size = 256 * 1024 * 1024; - config.mem2Size = 24 * 1024 * 1024; - config.allowJoystickBackgroundEvents = true; - config.imGuiInitCallback = &aurora_imgui_init_callback; - config.allowTextureReplacements = true; - config.allowTextureDumps = false; - + EnsureInitialPipelineCache(dusk::ConfigPath); PADSetDefaultMapping(&defaultPadMapping); - auroraInfo = aurora_initialize(argc, argv, &config); + { + const auto configPathString = dusk::ConfigPath.string(); + AuroraConfig config{}; + config.appName = dusk::AppName; + config.configPath = configPathString.c_str(); + config.vsync = dusk::getSettings().video.enableVsync; + config.startFullscreen = dusk::getSettings().video.enableFullscreen; + config.windowPosX = -1; + config.windowPosY = -1; + config.windowWidth = defaultWindowWidth * 2; + config.windowHeight = defaultWindowHeight * 2; + config.desiredBackend = ResolveDesiredBackend(parsed_arg_options); + config.logCallback = &aurora_log_callback; + config.logLevel = startupLogLevel; + config.mem1Size = 256 * 1024 * 1024; + config.mem2Size = 24 * 1024 * 1024; + config.allowJoystickBackgroundEvents = true; + config.imGuiInitCallback = &aurora_imgui_init_callback; + config.allowTextureReplacements = true; + config.allowTextureDumps = false; + auroraInfo = aurora_initialize(argc, argv, &config); + } + +#ifdef DUSK_DISCORD_RPC + dusk::discord::Initialize(); +#endif VISetWindowTitle( fmt::format("Dusk {} [{}]", DUSK_WC_DESCRIBE, dusk::backend_name(auroraInfo.backend)) .c_str()); if (dusk::getSettings().video.lockAspectRatio) { - VILockAspectRatio(defaultAspectRatioW, defaultAspectRatioH); + AuroraSetViewportPolicy(AURORA_VIEWPORT_FIT); } else { - VIUnlockAspectRatio(); + AuroraSetViewportPolicy(AURORA_VIEWPORT_STRETCH); } + VISetFrameBufferScale(dusk::getSettings().game.internalResolutionScale.getValue()); dusk::audio::SetMasterVolume(dusk::getSettings().audio.masterVolume / 100.0f); dusk::audio::SetEnableReverb(dusk::getSettings().audio.enableReverb); @@ -547,6 +579,9 @@ int game_main(int argc, char* argv[]) { // pre game launch ui main loop if (!launchUILoop()) { dusk::ShutdownCrashReporting(); +#ifdef DUSK_DISCORD_RPC + dusk::discord::Shutdown(); +#endif aurora_shutdown(); return 0; } @@ -584,6 +619,8 @@ int game_main(int argc, char* argv[]) { main01(); + dusk::MoviePlayerShutdown(); + dusk::ShutdownCrashReporting(); dusk::ShutdownFileLogging(); fflush(stdout); @@ -594,6 +631,9 @@ int game_main(int argc, char* argv[]) { // Notifies all CVs and causes threads to exit OSResetSystem(OS_RESET_SHUTDOWN, 0, 0); +#ifdef DUSK_DISCORD_RPC + dusk::discord::Shutdown(); +#endif aurora_shutdown(); return 0; diff --git a/tools/saves_to_states_json.py b/tools/saves_to_states_json.py new file mode 100644 index 0000000000..90032cf9d6 --- /dev/null +++ b/tools/saves_to_states_json.py @@ -0,0 +1,58 @@ +""" +Convert a folder of TPGZ saves to a states.json + +Usage: + python saves_to_states_json.py path/to/saves [prefix] + +Requirements: + pip install zstandard +""" + +import base64 +import json +import struct +import sys +import zstandard +from pathlib import Path + +SAVE_C_SIZE = 0x958 + +PACKET_FORMAT = "<8sbbh" + +RETURN_PLACE_OFF = 0x058 +NAME_OFF = RETURN_PLACE_OFF + 0x00 +ROOM_OFF = RETURN_PLACE_OFF + 0x09 +SPAWN_POINT_OFF = RETURN_PLACE_OFF + 0x08 + +folder = Path(sys.argv[1]) if len(sys.argv) > 1 else Path(__file__).parent +out_path = folder / "states.json" + +if len(sys.argv) > 2: + prefix = sys.argv[2] +else: + prefix = None + +cctx = zstandard.ZstdCompressor(level=1) +states = [] + +for bin_path in sorted(folder.glob("*.bin")): + raw = bin_path.read_bytes() + save_c = raw[:SAVE_C_SIZE] + if len(save_c) < SAVE_C_SIZE: + print(f" skip {bin_path.name}: too small ({len(save_c)} bytes)") + continue + + stage_name = save_c[NAME_OFF:NAME_OFF + 8] + room_no = struct.unpack_from("b", save_c, ROOM_OFF)[0] + spawn_point = struct.unpack_from("B", save_c, SPAWN_POINT_OFF)[0] + + pkt = struct.pack(PACKET_FORMAT, stage_name, room_no, -1, spawn_point) + payload = pkt + save_c + encoded = base64.b64encode(cctx.compress(payload)).decode("ascii") + + stage_str = stage_name.rstrip(b"\x00").decode("ascii", errors="replace") + print(f" {bin_path.stem:30s} stage={stage_str!r} room={room_no} point={spawn_point}") + states.append({"name": f"({prefix}) {bin_path.stem}" if prefix else bin_path.stem, "data": encoded}) + +out_path.write_text(json.dumps(states, indent=2)) +print(f"\nWrote {len(states)} states to {out_path}")