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 1dbaefd7d3..46f0b1a411 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) @@ -87,6 +90,11 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_POSITION_INDEPENDENT_CODE ON) set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON) +# Folder-based instead of target-based organization +# in Visual Studio and Xcode generators +set_property(GLOBAL PROPERTY USE_FOLDERS ON) +set_property(GLOBAL PROPERTY PREDEFINED_TARGETS_FOLDER "_cmake") + if (CMAKE_SYSTEM_NAME STREQUAL Linux) set(DAWN_USE_WAYLAND ON CACHE BOOL "Enable support for Wayland surface" FORCE) endif () @@ -125,19 +133,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,27 +275,21 @@ 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") set(DUSK_COPYRIGHT "Copyright (C) Twilit Realm contributors") -set(DUSK_GAME_NAME "GZ2E") -set(DUSK_GAME_VERSION "01") -set(DUSK_TP_VERSION ${DUSK_GAME_NAME}${DUSK_GAME_VERSION}) -message(STATUS "dusk: Game Version: ${DUSK_TP_VERSION}") - -source_group("dolzel" FILES ${DOLZEL_FILES} ${Z2AUDIOLIB_FILES} ${JSYSTEM_FILES} ${JSYSTEM_DEBUG_FILES} ${REL_FILES}) +source_group("dolzel" FILES ${DOLZEL_FILES} ${Z2AUDIOLIB_FILES} ${REL_FILES}) source_group("dusk" FILES ${DUSK_FILES}) -set(GAME_COMPILE_DEFS TARGET_PC WIDESCREEN_SUPPORT=1 AVOID_UB=1 VERSION=0 - DUSK_TP_VERSION="${DUSK_TP_VERSION}" DUSK_GAME_NAME="${DUSK_GAME_NAME}" DUSK_GAME_VERSION="${DUSK_GAME_VERSION}") +set(GAME_COMPILE_DEFS TARGET_PC WIDESCREEN_SUPPORT=1 AVOID_UB=1 VERSION=0) set(GAME_INCLUDE_DIRS include src - assets/${DUSK_TP_VERSION} + assets/GZ2E01 # TODO: make this dynamic if needed? libs/JSystem/include libs extern/aurora/include/dolphin @@ -277,7 +297,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) @@ -286,6 +306,10 @@ if (DUSK_ENABLE_SENTRY_NATIVE) list(APPEND GAME_COMPILE_DEFS DUSK_ENABLE_SENTRY_NATIVE=1 SENTRY_BUILD_STATIC=1) endif () +if (WIN32) + list(APPEND GAME_LIBS Ws2_32) +endif () + if (DUSK_MOVIE_SUPPORT) if (TARGET libjpeg-turbo::turbojpeg-static) list(APPEND GAME_LIBS libjpeg-turbo::turbojpeg-static) @@ -295,6 +319,48 @@ 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") @@ -311,47 +377,65 @@ endif () # game_debug is for game code files that we know work when compiled with DEBUG=1 # Of course, if building a release build, this distinction is irrelevant -add_library(game_debug OBJECT ${JSYSTEM_DEBUG_FILES} ${SSYSTEM_FILES} +set(GAME_DEBUG_FILES + ${SSYSTEM_FILES} src/dusk/audio/DuskAudioSystem.cpp src/dusk/audio/JASCriticalSection.cpp src/dusk/audio/DuskDsp.cpp src/dusk/audio/Adpcm.cpp src/dusk/audio/DspStub.cpp - src/dusk/imgui/ImGuiAudio.cpp) + src/dusk/imgui/ImGuiAudio.cpp +) +set_source_files_properties( + ${GAME_DEBUG_FILES} + PROPERTIES + COMPILE_DEFINITIONS "$<$:DEBUG=1>;$<$:PARTIAL_DEBUG=1>" +) # game_base is for all other game code files -add_library(game_base OBJECT ${DOLZEL_FILES} ${Z2AUDIOLIB_FILES} ${JSYSTEM_FILES} ${REL_FILES} ${DUSK_FILES} ${DOLPHIN_FILES}) +set(GAME_BASE_FILES + ${DOLZEL_FILES} + ${Z2AUDIOLIB_FILES} + ${REL_FILES} + ${DUSK_FILES} + ${DOLPHIN_FILES} +) +set_source_files_properties( + ${GAME_BASE_FILES} + PROPERTIES + COMPILE_DEFINITIONS "NDEBUG=1;NDEBUG_DEFINED=1;DEBUG_DEFINED=0;$<$:PARTIAL_DEBUG=1>" +) -target_compile_definitions(game_debug PRIVATE ${GAME_COMPILE_DEFS} $<$:DEBUG=1> $<$:PARTIAL_DEBUG=1>) -target_compile_definitions(game_base PRIVATE ${GAME_COMPILE_DEFS} NDEBUG=1 NDEBUG_DEFINED=1 DEBUG_DEFINED=0 $<$:PARTIAL_DEBUG=1>) +foreach(jsystem_lib IN LISTS JSYSTEM_LIBRARIES) + target_compile_definitions(${jsystem_lib} PRIVATE + ${GAME_COMPILE_DEFS} + $<$:DEBUG=1> + $<$:PARTIAL_DEBUG=1> + ) + target_include_directories(${jsystem_lib} PRIVATE ${GAME_INCLUDE_DIRS}) + target_link_libraries(${jsystem_lib} PRIVATE ${GAME_LIBS}) + set_target_properties(${jsystem_lib} PROPERTIES FOLDER "JSystem") +endforeach() -# only apply PCH to game_base since not all headers are necessarily validated with DEBUG=1 -target_precompile_headers(game_base PRIVATE "$<$:${CMAKE_SOURCE_DIR}/include/dusk_pch.hpp>") - -target_include_directories(game_debug PRIVATE ${GAME_INCLUDE_DIRS}) -target_include_directories(game_base PRIVATE ${GAME_INCLUDE_DIRS}) - -# This implicitly pulls in the library include directories even though no -# linking actually takes place for object libraries -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) -else () - add_executable(dusk src/dusk/main.cpp) +set(JSYSTEM_LINK_LIBRARIES ${JSYSTEM_LIBRARIES}) +if (CMAKE_CXX_LINK_GROUP_USING_RESCAN_SUPPORTED OR CMAKE_LINK_GROUP_USING_RESCAN_SUPPORTED) + # GNU ld resolves static archives in a single left-to-right pass. The split + # JSystem libraries reference each other, so they need a RESCAN group there. + set(JSYSTEM_LINK_LIBRARIES "$") 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) +set(DUSK_FILES src/dusk/main.cpp ${GAME_BASE_FILES} ${GAME_DEBUG_FILES}) +if(ANDROID) + add_library(dusk SHARED ${DUSK_FILES}) + set_target_properties(dusk PROPERTIES OUTPUT_NAME main) +else () + add_executable(dusk ${DUSK_FILES}) +endif () + +target_compile_definitions(dusk PRIVATE ${GAME_COMPILE_DEFS}) +target_include_directories(dusk PRIVATE ${GAME_INCLUDE_DIRS}) +target_link_libraries(dusk PRIVATE aurora::main ${GAME_LIBS} ${JSYSTEM_LINK_LIBRARIES}) +target_precompile_headers(dusk PRIVATE "$<$:${CMAKE_SOURCE_DIR}/include/dusk_pch.hpp>") if (TARGET crashpad_handler) add_dependencies(dusk crashpad_handler) endif () @@ -362,12 +446,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 +487,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 +578,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 +602,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..b6d6cec863 100644 --- a/README.md +++ b/README.md @@ -1,103 +1,31 @@ -## 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 | +| GameCube PAL | 2601822a488eeb86fb89db16ca8f29c2c953e1ca | -![CLion](assets/clion.png) +### 2. Download [Dusk](https://github.com/TwilitRealm/dusk/releases) -**Visual Studio (Windows)** +### 3. Setup the game +- Extract the zip folder +- Launch Dusk +- Select Options, then set the ISO Path to your supported game dump +- Press Start Game to play! -Open the project directory in Visual Studio. The CMake configuration will be loaded automatically. +![Dusk options](assets/dusk_options.png) -**ninja (macOS)** +# Building +If you'd like to build Dusk from source, please read the [build instructions](docs/building.md). -```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. - -#### 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"` +# Credits +Special thanks to the [TP decompilation](https://github.com/zeldaret/tp) team, the GC/Wii decompilation community, the [Aurora](https://github.com/encounter/aurora) developers, the [TP speedrunning community](https://zsrtp.link), and all [contributors](https://github.com/TwilitRealm/dusk/graphs/contributors). diff --git a/assets/dusk_options.png b/assets/dusk_options.png new file mode 100644 index 0000000000..4ed4ad7563 Binary files /dev/null and b/assets/dusk_options.png differ diff --git a/cmake-variants.yaml b/cmake-variants.yaml index 62d2642311..37edcede95 100644 --- a/cmake-variants.yaml +++ b/cmake-variants.yaml @@ -13,13 +13,3 @@ buildType: short: RelWithDebInfo long: Optimized, with debug symbols buildType: RelWithDebInfo - -tp_version: - default: GZ2E01 - description: TP Version - choices: - GZ2E01: - short: GZ2E01 - long: GZ2E01 - settings: - DUSK_TP_VERSION: GZ2E01 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 aa83f6d915..a6a3d3a65a 160000 --- a/extern/aurora +++ b/extern/aurora @@ -1 +1 @@ -Subproject commit aa83f6d91545c304b8f62a2965c7dcd1ec8b511b +Subproject commit a6a3d3a65ae0de6de8b60629cf47fd0f446c21cb diff --git a/files.cmake b/files.cmake index e91415b3be..f91756a9d5 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 @@ -315,7 +314,7 @@ set(SSYSTEM_FILES src/SSystem/SStandard/s_basic.cpp ) -set(JSYSTEM_DEBUG_FILES +add_library(JSystem_JParticle STATIC libs/JSystem/src/JParticle/JPAResourceManager.cpp libs/JSystem/src/JParticle/JPAResource.cpp libs/JSystem/src/JParticle/JPABaseShape.cpp @@ -331,10 +330,19 @@ set(JSYSTEM_DEBUG_FILES libs/JSystem/src/JParticle/JPAEmitter.cpp libs/JSystem/src/JParticle/JPAParticle.cpp libs/JSystem/src/JParticle/JPAMath.cpp +) + +add_library(JSystem_JFramework STATIC libs/JSystem/src/JFramework/JFWSystem.cpp libs/JSystem/src/JFramework/JFWDisplay.cpp +) + +add_library(JSystem_J3DU STATIC libs/JSystem/src/J3DU/J3DUClipper.cpp libs/JSystem/src/J3DU/J3DUDL.cpp +) + +add_library(JSystem_JKernel STATIC libs/JSystem/src/JKernel/JKRHeap.cpp libs/JSystem/src/JKernel/JKRExpHeap.cpp libs/JSystem/src/JKernel/JKRSolidHeap.cpp @@ -360,14 +368,23 @@ set(JSYSTEM_DEBUG_FILES libs/JSystem/src/JKernel/JKRDvdRipper.cpp libs/JSystem/src/JKernel/JKRDvdAramRipper.cpp libs/JSystem/src/JKernel/JKRDecomp.cpp +) + +add_library(JSystem_JMath STATIC libs/JSystem/src/JMath/JMath.cpp libs/JSystem/src/JMath/random.cpp libs/JSystem/src/JMath/JMATrigonometric.cpp +) + +add_library(JSystem_JSupport STATIC libs/JSystem/src/JSupport/JSUList.cpp libs/JSystem/src/JSupport/JSUInputStream.cpp libs/JSystem/src/JSupport/JSUOutputStream.cpp libs/JSystem/src/JSupport/JSUMemoryStream.cpp libs/JSystem/src/JSupport/JSUFileStream.cpp +) + +add_library(JSystem_JUtility STATIC libs/JSystem/src/JUtility/JUTCacheFont.cpp libs/JSystem/src/JUtility/JUTResource.cpp libs/JSystem/src/JUtility/JUTTexture.cpp @@ -388,6 +405,9 @@ set(JSYSTEM_DEBUG_FILES libs/JSystem/src/JUtility/JUTConsole.cpp libs/JSystem/src/JUtility/JUTDirectFile.cpp libs/JSystem/src/JUtility/JUTFontData_Ascfont_fix12.cpp +) + +add_library(JSystem_JStage STATIC libs/JSystem/src/JStage/JSGActor.cpp libs/JSystem/src/JStage/JSGAmbientLight.cpp libs/JSystem/src/JStage/JSGCamera.cpp @@ -395,6 +415,9 @@ set(JSYSTEM_DEBUG_FILES libs/JSystem/src/JStage/JSGLight.cpp libs/JSystem/src/JStage/JSGObject.cpp libs/JSystem/src/JStage/JSGSystem.cpp +) + +add_library(JSystem_J2DGraph STATIC libs/JSystem/src/J2DGraph/J2DGrafContext.cpp libs/JSystem/src/J2DGraph/J2DOrthoGraph.cpp libs/JSystem/src/J2DGraph/J2DTevs.cpp @@ -413,6 +436,9 @@ set(JSYSTEM_DEBUG_FILES libs/JSystem/src/J2DGraph/J2DAnmLoader.cpp libs/JSystem/src/J2DGraph/J2DAnimation.cpp libs/JSystem/src/J2DGraph/J2DManage.cpp +) + +add_library(JSystem_J3DGraphBase STATIC libs/JSystem/src/J3DGraphBase/J3DGD.cpp libs/JSystem/src/J3DGraphBase/J3DSys.cpp libs/JSystem/src/J3DGraphBase/J3DVertex.cpp @@ -427,6 +453,9 @@ set(JSYSTEM_DEBUG_FILES libs/JSystem/src/J3DGraphBase/J3DTevs.cpp libs/JSystem/src/J3DGraphBase/J3DDrawBuffer.cpp libs/JSystem/src/J3DGraphBase/J3DStruct.cpp +) + +add_library(JSystem_J3DGraphAnimator STATIC libs/JSystem/src/J3DGraphAnimator/J3DShapeTable.cpp libs/JSystem/src/J3DGraphAnimator/J3DJointTree.cpp libs/JSystem/src/J3DGraphAnimator/J3DModelData.cpp @@ -438,6 +467,9 @@ set(JSYSTEM_DEBUG_FILES libs/JSystem/src/J3DGraphAnimator/J3DCluster.cpp libs/JSystem/src/J3DGraphAnimator/J3DJoint.cpp libs/JSystem/src/J3DGraphAnimator/J3DMaterialAttach.cpp +) + +add_library(JSystem_J3DGraphLoader STATIC libs/JSystem/src/J3DGraphLoader/J3DMaterialFactory.cpp libs/JSystem/src/J3DGraphLoader/J3DMaterialFactory_v21.cpp libs/JSystem/src/J3DGraphLoader/J3DClusterLoader.cpp @@ -446,6 +478,9 @@ set(JSYSTEM_DEBUG_FILES libs/JSystem/src/J3DGraphLoader/J3DJointFactory.cpp libs/JSystem/src/J3DGraphLoader/J3DShapeFactory.cpp libs/JSystem/src/J3DGraphLoader/J3DAnmLoader.cpp +) + +add_library(JSystem_JStudio STATIC libs/JSystem/src/JStudio/JStudio/ctb.cpp libs/JSystem/src/JStudio/JStudio/ctb-data.cpp libs/JSystem/src/JStudio/JStudio/functionvalue.cpp @@ -460,6 +495,9 @@ set(JSYSTEM_DEBUG_FILES libs/JSystem/src/JStudio/JStudio/stb.cpp libs/JSystem/src/JStudio/JStudio/stb-data-parse.cpp libs/JSystem/src/JStudio/JStudio/stb-data.cpp +) + +add_library(JSystem_JStudio_JStage STATIC libs/JSystem/src/JStudio/JStudio_JStage/control.cpp libs/JSystem/src/JStudio/JStudio_JStage/object.cpp libs/JSystem/src/JStudio/JStudio_JStage/object-actor.cpp @@ -467,10 +505,19 @@ set(JSYSTEM_DEBUG_FILES libs/JSystem/src/JStudio/JStudio_JStage/object-camera.cpp libs/JSystem/src/JStudio/JStudio_JStage/object-fog.cpp libs/JSystem/src/JStudio/JStudio_JStage/object-light.cpp +) + +add_library(JSystem_JStudio_JAudio2 STATIC libs/JSystem/src/JStudio/JStudio_JAudio2/control.cpp libs/JSystem/src/JStudio/JStudio_JAudio2/object-sound.cpp +) + +add_library(JSystem_JStudio_JParticle STATIC libs/JSystem/src/JStudio/JStudio_JParticle/control.cpp libs/JSystem/src/JStudio/JStudio_JParticle/object-particle.cpp +) + +add_library(JSystem_JAudio2 STATIC libs/JSystem/src/JAudio2/JASCalc.cpp libs/JSystem/src/JAudio2/JASTaskThread.cpp libs/JSystem/src/JAudio2/JASDvdThread.cpp @@ -535,22 +582,34 @@ set(JSYSTEM_DEBUG_FILES libs/JSystem/src/JAudio2/JAUSoundAnimator.cpp libs/JSystem/src/JAudio2/JAUSoundTable.cpp libs/JSystem/src/JAudio2/JAUStreamFileTable.cpp +) + +add_library(JSystem_JMessage STATIC libs/JSystem/src/JMessage/control.cpp libs/JSystem/src/JMessage/data.cpp libs/JSystem/src/JMessage/processor.cpp libs/JSystem/src/JMessage/resource.cpp libs/JSystem/src/JMessage/locale.cpp +) + +add_library(JSystem_JGadget STATIC libs/JSystem/src/JGadget/binary.cpp libs/JSystem/src/JGadget/define.cpp libs/JSystem/src/JGadget/linklist.cpp libs/JSystem/src/JGadget/search.cpp libs/JSystem/src/JGadget/std-vector.cpp +) + +add_library(JSystem_JAHostIO STATIC libs/JSystem/src/JAHostIO/JAHFrameNode.cpp libs/JSystem/src/JAHostIO/JAHioMessage.cpp libs/JSystem/src/JAHostIO/JAHioMgr.cpp libs/JSystem/src/JAHostIO/JAHioNode.cpp libs/JSystem/src/JAHostIO/JAHioUtil.cpp libs/JSystem/src/JAHostIO/JAHVirtualNode.cpp +) + +add_library(JSystem_JHostIO STATIC libs/JSystem/src/JHostIO/JORFile.cpp libs/JSystem/src/JHostIO/JORHostInfo.cpp libs/JSystem/src/JHostIO/JORMessageBox.cpp @@ -560,7 +619,28 @@ set(JSYSTEM_DEBUG_FILES libs/JSystem/src/JHostIO/JHIMccBuf.cpp ) -set(JSYSTEM_FILES +set(JSYSTEM_LIBRARIES + JSystem_JParticle + JSystem_JFramework + JSystem_J3DU + JSystem_JKernel + JSystem_JMath + JSystem_JSupport + JSystem_JUtility + JSystem_JStage + JSystem_J2DGraph + JSystem_J3DGraphBase + JSystem_J3DGraphAnimator + JSystem_J3DGraphLoader + JSystem_JStudio + JSystem_JStudio_JStage + JSystem_JStudio_JAudio2 + JSystem_JStudio_JParticle + JSystem_JAudio2 + JSystem_JMessage + JSystem_JGadget + JSystem_JAHostIO + JSystem_JHostIO ) set(REL_FILES @@ -1334,6 +1414,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 @@ -1341,10 +1422,13 @@ set(DUSK_FILES src/dusk/crash_reporting.cpp 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.cpp + src/dusk/gamepad_color.cpp src/dusk/io.cpp src/dusk/layout.cpp src/dusk/logging.cpp @@ -1362,8 +1446,6 @@ set(DUSK_FILES 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 @@ -1378,9 +1460,15 @@ set(DUSK_FILES src/dusk/imgui/ImGuiSaveEditor.cpp src/dusk/imgui/ImGuiStateShare.hpp src/dusk/imgui/ImGuiStateShare.cpp + src/dusk/imgui/ImGuiAchievements.hpp + src/dusk/imgui/ImGuiAchievements.cpp + src/dusk/achievements.cpp src/dusk/iso_validate.cpp + src/dusk/livesplit.cpp src/dusk/offset_ptr.cpp src/dusk/OSContext.cpp src/dusk/OSThread.cpp src/dusk/OSMutex.cpp + src/dusk/discord_presence.cpp + src/dusk/version.cpp ) diff --git a/include/d/actor/d_a_alink.h b/include/d/actor/d_a_alink.h index 53228af84b..9980ab776f 100644 --- a/include/d/actor/d_a_alink.h +++ b/include/d/actor/d_a_alink.h @@ -4551,7 +4551,7 @@ public: #if TARGET_PC void handleWolfHowl(); void handleQuickTransform(); - bool checkGyroAimItemContext(); + bool checkGyroAimContext(); #endif }; // Size: 0x385C diff --git a/include/d/actor/d_a_b_gnd.h b/include/d/actor/d_a_b_gnd.h index b9c8781ec6..0827102eec 100644 --- a/include/d/actor/d_a_b_gnd.h +++ b/include/d/actor/d_a_b_gnd.h @@ -188,6 +188,15 @@ public: /* 0x273C */ f32 mKankyoBlend; /* 0x2740 */ u8 field_0x2740; /* 0x2744 */ dMsgFlow_c mMsgFlow; +#if TARGET_PC + cXyz mReinsInterpPrev[2][16]; + cXyz mReinsInterpCurr[2][16]; + cXyz mReinsTexInterpPrev[2]; + cXyz mReinsTexInterpCurr[2]; + bool mReinsInterpPrevValid; + bool mReinsInterpCurrValid; + s8 mDemoCamSyncTicks; +#endif }; STATIC_ASSERT(sizeof(b_gnd_class) == 0x2790); diff --git a/include/d/actor/d_a_e_mb.h b/include/d/actor/d_a_e_mb.h index f36c744879..527cf30eac 100644 --- a/include/d/actor/d_a_e_mb.h +++ b/include/d/actor/d_a_e_mb.h @@ -44,6 +44,12 @@ public: /* 0x88C */ u8 field_0x88C[0x8C8 - 0x88C]; /* 0x8C8 */ s8 field_0x8c8; /* 0x8C9 */ u8 mInitHIO; +#if TARGET_PC + cXyz mRopeInterpPrev[16]; + cXyz mRopeInterpCurr[16]; + bool mRopeInterpPrevValid; + bool mRopeInterpCurrValid; +#endif }; STATIC_ASSERT(sizeof(e_mb_class) == 0x8cc); 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_cam_param.h b/include/d/d_cam_param.h index d6ff7b46d7..867c66e38d 100644 --- a/include/d/d_cam_param.h +++ b/include/d/d_cam_param.h @@ -143,6 +143,12 @@ public: /* 0x20 */ JORFile mFile; #endif +#if TARGET_PC + /* 0x24 */ u8 mManualMode; + /* 0x25 */ f32 freeXAngle; + /* 0x29 */ f32 freeYAngle; +#endif + u32 Id(s32 i_style) { return mCamStyleData[i_style].field_0x0; } int Algorythmn(s32 i_style) { return mCamStyleData[i_style].field_0x4; } int Algorythmn() { return mCurrentStyle->field_0x4; } diff --git a/include/d/d_camera.h b/include/d/d_camera.h index 85e930c395..105c9cae9f 100644 --- a/include/d/d_camera.h +++ b/include/d/d_camera.h @@ -273,6 +273,8 @@ public: /* 0xA4 */ f32 field_0xa4; /* 0xA8 */ int field_0xa8; /* 0xAC */ f32 field_0xac; + f32 xAngle; + f32 yAngle; }; struct LockOnData { @@ -1024,6 +1026,9 @@ public: bool colosseumCamera(s32); bool test1Camera(s32); bool test2Camera(s32); + #if TARGET_PC + bool freeCamera(); + #endif bool towerCamera(s32); bool hookshotCamera(s32); bool railCamera(s32); 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_dmap.h b/include/d/d_menu_dmap.h index 78c659e2cf..e50c533654 100644 --- a/include/d/d_menu_dmap.h +++ b/include/d/d_menu_dmap.h @@ -103,6 +103,10 @@ public: field_0xd98 = param_1; } +#if TARGET_PC + void resetScrollArrowMask() { field_0xdda = 0; } +#endif + /* 0xC98 */ JKRExpHeap* mpHeap; /* 0xC9C */ JKRExpHeap* mpTalkHeap; /* 0xCA0 */ STControl* mpStick; 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..74624eac80 100644 --- a/include/d/d_menu_ring.h +++ b/include/d/d_menu_ring.h @@ -204,6 +204,18 @@ private: /* 0x6D1 */ u8 field_0x6d1; /* 0x6D2 */ u8 field_0x6d2; /* 0x6D3 */ u8 field_0x6d3; +#if TARGET_PC + f32 mSelectItemSlideElapsed[4]; + f32 mCursorInterpPrevX; + f32 mCursorInterpPrevY; + f32 mCursorInterpCurrX; + f32 mCursorInterpCurrY; + s16 mCursorInterpPrevAngle; + s16 mCursorInterpCurrAngle; + bool mCursorInterpPrevAngular; + bool mCursorInterpCurrAngular; + bool mCursorInterpInit; +#endif }; #endif /* D_MENU_D_MENU_RING_H */ diff --git a/include/d/d_meter_button.h b/include/d/d_meter_button.h index 3028abeab5..0f05d32440 100644 --- a/include/d/d_meter_button.h +++ b/include/d/d_meter_button.h @@ -343,6 +343,11 @@ public: /* 0x624 */ f32 mMidonaPosX; /* 0x628 */ f32 mMidonaPosY; /* 0x62C */ f32 mMidonaScale; + +#ifdef TARGET_PC + bool mWasListen[2]; + bool mWasRepeat[2]; +#endif }; #endif /* D_METER_D_METER_BUTTON_H */ diff --git a/include/d/d_name.h b/include/d/d_name.h index e685871738..3ac6592457 100644 --- a/include/d/d_name.h +++ b/include/d/d_name.h @@ -89,7 +89,7 @@ public: void MojiSelectAnm3(); int mojiChange(u8); void selectMojiSet(); - #if REGION_JPN + #if TARGET_PC || REGION_JPN int checkDakuon(int, u8); int setDakuon(int, u8); #endif diff --git a/include/d/d_s_logo.h b/include/d/d_s_logo.h index 56d10eef82..ab714a17ae 100644 --- a/include/d/d_s_logo.h +++ b/include/d/d_s_logo.h @@ -79,7 +79,7 @@ public: bool isProgressiveMode(); void setRenderMode(); - #if VERSION == VERSION_GCN_PAL || PLATFORM_WII || PLATFORM_SHIELD + #if TARGET_PC || VERSION == VERSION_GCN_PAL || PLATFORM_WII || PLATFORM_SHIELD u8 getPalLanguage(); #endif @@ -149,7 +149,7 @@ public: /* 0x200 */ dDlst_2D_c* mNvLogo; /* 0x204 */ dDlst_2D_c* mMocImg; #endif -#if VERSION == VERSION_GCN_PAL +#if TARGET_PC || VERSION == VERSION_GCN_PAL /* 0x1FC */ mDoDvdThd_mountArchive_c* mpPalLogoResCommand; #endif /* 0x1FC */ request_of_phase_process_class* m_preLoad_dylPhase; diff --git a/include/d/d_select_cursor.h b/include/d/d_select_cursor.h index c77da550f8..1ca226611b 100644 --- a/include/d/d_select_cursor.h +++ b/include/d/d_select_cursor.h @@ -47,6 +47,13 @@ public: mPositionY = y; } +#ifdef TARGET_PC + f32 getPositionX() const { return mPositionX; } + f32 getPositionY() const { return mPositionY; } + + void refreshAspectScale(); +#endif + void onUpdateFlag() { mUpdateFlag = true; } void resetUpdateFlag() { mUpdateFlag = false; } @@ -79,6 +86,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/achievements.h b/include/dusk/achievements.h new file mode 100644 index 0000000000..cd4294b6f1 --- /dev/null +++ b/include/dusk/achievements.h @@ -0,0 +1,65 @@ +#pragma once + +#include +#include +#include +#include +#include +#include "nlohmann/json.hpp" + +namespace dusk { + +enum class AchievementCategory : uint8_t { + Story, + Collection, + Challenge, + Minigame, + Glitched +}; + +struct Achievement { + const char* key; + const char* name; + const char* description; + AchievementCategory category; + bool isCounter; + int32_t goal; + int32_t progress; + bool unlocked; +}; + +// Responsible for updating a.progress. +// Use extra for any per-achievement state that must survive across frames or sessions, extra is saved +using AchievementCheckFn = std::function; + +class AchievementSystem { +public: + static AchievementSystem& get(); + + void load(); + void save(); + void tick(); + void clearAll(); + + std::vector getAchievements() const; + bool hasPendingUnlock() const { return !m_pendingUnlocks.empty(); } + std::string consumePendingUnlock(); + +private: + struct Entry { + Achievement achievement; + AchievementCheckFn check; + nlohmann::json extra; + }; + + AchievementSystem(); + static std::vector makeEntries(); + void processEntry(Entry& e); + + std::vector m_entries; + bool m_loaded = false; + bool m_dirty = false; + std::queue m_pendingUnlocks; +}; + +} // namespace dusk 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/dvd_asset.hpp b/include/dusk/dvd_asset.hpp index 014c683d44..8594d4631c 100644 --- a/include/dusk/dvd_asset.hpp +++ b/include/dusk/dvd_asset.hpp @@ -1,22 +1,31 @@ #pragma once #include "dolphin/types.h" +#include "version.hpp" namespace dusk { +struct OffsetVersion { + version::GameVersion mGameVersion; + u32 mOffset; + + constexpr OffsetVersion(const version::GameVersion gameVersion, const u32 offset) + : mGameVersion(gameVersion), mOffset(offset) {} +}; + /** * Load bytes from the main DOL by GameCube virtual address */ -bool LoadDolAsset(void* dst, u32 virtualAddress, s32 size); +bool LoadDolAsset(void* dst, std::initializer_list virtualAddress, s32 size); /** * Load bytes from a REL file in the ISO filesystem, dst must be 32-byte aligned */ -bool LoadRelAsset(void* dst, const char* dvdPath, s32 offset, s32 size); +bool LoadRelAsset(void* dst, const char* dvdPath, std::initializer_list offset, s32 size); /** * Load bytes from a REL inside RELS.arc */ -bool LoadArchivedRelAsset(void* dst, u32 memType, const char* relFileName, s32 offset, s32 size); +bool LoadArchivedRelAsset(void* dst, u32 memType, const char* relFileName, std::initializer_list offset, s32 size); } // namespace dusk diff --git a/include/dusk/frame_interpolation.h b/include/dusk/frame_interpolation.h index 383d16d288..8c19e7e59b 100644 --- a/include/dusk/frame_interpolation.h +++ b/include/dusk/frame_interpolation.h @@ -1,5 +1,4 @@ -#ifndef DUSK_FRAME_INTERP_H -#define DUSK_FRAME_INTERP_H +#pragma once #include #include @@ -7,6 +6,7 @@ #include class camera_process_class; +class view_class; #ifdef __cplusplus namespace dusk { @@ -16,42 +16,37 @@ void ensure_initialized(); 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); +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(); -struct PresentationCameraScope { - PresentationCameraScope() { begin_presentation_camera(); } - ~PresentationCameraScope() { end_presentation_camera(); } - PresentationCameraScope(const PresentationCameraScope&) = delete; - PresentationCameraScope& operator=(const PresentationCameraScope&) = delete; - PresentationCameraScope(PresentationCameraScope&&) = delete; - PresentationCameraScope& operator=(PresentationCameraScope&&) = delete; -}; - -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/gamepad_color.h b/include/dusk/gamepad_color.h new file mode 100644 index 0000000000..c7cfc1e716 --- /dev/null +++ b/include/dusk/gamepad_color.h @@ -0,0 +1,8 @@ +#pragma once + +#ifndef GAMEPAD_COLOR_H +#define GAMEPAD_COLOR_H + +void handleGamepadColor(); + +#endif \ No newline at end of file diff --git a/include/dusk/gyro.h b/include/dusk/gyro.h index 5636d34b05..279aeae16e 100644 --- a/include/dusk/gyro.h +++ b/include/dusk/gyro.h @@ -3,11 +3,11 @@ namespace dusk::gyro { void read(float dt); -void consumeAimDeltas(float& out_yaw_rad, float& out_pitch_rad); -bool queryGyroAimItemContext(); +void getAimDeltas(float& out_yaw, float& out_pitch); +bool queryGyroAimContext(); void rollgoalTick(bool play_active, s16 camera_yaw); -void rollgoalTableOffset(s16& out_add_x, s16& out_add_z); +void rollgoalTableOffset(s16& out_ax, s16& out_az); extern bool s_sensor_keep_alive; bool get_sensor_keep_alive(); diff --git a/include/dusk/livesplit.h b/include/dusk/livesplit.h new file mode 100644 index 0000000000..b283a29af4 --- /dev/null +++ b/include/dusk/livesplit.h @@ -0,0 +1,16 @@ +#pragma once + +#include + +namespace dusk::speedrun { +void onGameFrame(); +uint64_t getFrameCount(); +void start(); +void reset(); +void connectLiveSplit(const char* host = "127.0.0.1", int port = 16834); +void disconnectLiveSplit(); +bool consumeConnectedEvent(); +bool consumeDisconnectedEvent(); +void updateLiveSplit(); +void shutdown(); +} 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 7bfc65a312..2acc69e43c 100644 --- a/include/dusk/settings.h +++ b/include/dusk/settings.h @@ -13,12 +13,26 @@ enum class BloomMode : int { Dusk = 2, }; +enum class GameLanguage : u8 { + English = OS_LANGUAGE_ENGLISH, + German = OS_LANGUAGE_GERMAN, + French = OS_LANGUAGE_FRENCH, + Spanish = OS_LANGUAGE_SPANISH, + Italian = OS_LANGUAGE_ITALIAN, +}; + namespace config { template <> struct ConfigEnumRange { static constexpr auto min = BloomMode::Off; static constexpr auto max = BloomMode::Dusk; }; + +template <> +struct ConfigEnumRange { + static constexpr auto min = GameLanguage::English; + static constexpr auto max = GameLanguage::Italian; +}; } // Persistent user settings @@ -46,6 +60,8 @@ struct UserSettings { // Game settings struct { + ConfigVar language; + // QoL ConfigVar enableQuickTransform; ConfigVar hideTvSettingsScreen; @@ -61,19 +77,26 @@ struct UserSettings { ConfigVar noMissClimbing; ConfigVar fastTears; ConfigVar instantSaves; + ConfigVar instantText; ConfigVar sunsSong; // Preferences ConfigVar enableMirrorMode; - ConfigVar invertCameraXAxis; ConfigVar disableMainHUD; + ConfigVar pauseOnFocusLost; + ConfigVar enableLinkDollRotation; + ConfigVar enableAchievementNotifications; + // Graphics ConfigVar bloomMode; ConfigVar bloomMultiplier; - ConfigVar enableWaterRefraction; + ConfigVar disableWaterRefraction; ConfigVar enableFrameInterpolation; + ConfigVar internalResolutionScale; ConfigVar shadowResolutionMultiplier; + ConfigVar enableDepthOfField; + ConfigVar enableMapBackground; // Audio ConfigVar noLowHpSound; @@ -82,13 +105,29 @@ struct UserSettings { // Input ConfigVar enableGyroAim; ConfigVar enableGyroRollgoal; - ConfigVar gyroAimSensitivityX; - ConfigVar gyroAimSensitivityY; - ConfigVar gyroRollgoalSensitivity; - ConfigVar gyroAimInvertPitch; - ConfigVar gyroAimInvertYaw; + ConfigVar gyroSensitivityX; + ConfigVar gyroSensitivityY; + ConfigVar gyroSensitivityRollgoal; + ConfigVar gyroSmoothing; + ConfigVar gyroDeadband; + ConfigVar gyroInvertPitch; + ConfigVar gyroInvertYaw; + ConfigVar freeCamera; + ConfigVar invertCameraXAxis; + ConfigVar invertCameraYAxis; + ConfigVar freeCameraSensitivity; // Cheats + ConfigVar infiniteHearts; + ConfigVar infiniteArrows; + ConfigVar infiniteBombs; + ConfigVar infiniteOil; + ConfigVar infiniteOxygen; + ConfigVar infiniteRupees; + ConfigVar enableIndefiniteItemDrops; + ConfigVar moonJump; + ConfigVar superClawshot; + ConfigVar alwaysGreatspin; ConfigVar enableFastIronBoots; ConfigVar canTransformAnywhere; ConfigVar fastSpinner; @@ -99,6 +138,10 @@ struct UserSettings { // Controls ConfigVar enableTurboKeybind; + + // Tools + ConfigVar speedrunMode; + ConfigVar liveSplitEnabled; } game; struct { @@ -108,6 +151,8 @@ struct UserSettings { ConfigVar showPipelineCompilation; ConfigVar wasPresetChosen; ConfigVar enableCrashReporting; + ConfigVar duskMenuOpen; + ConfigVar cardFileType; } backend; }; @@ -132,6 +177,7 @@ struct TransientSettings { CollisionViewSettings collisionView; bool skipFrameRateLimit; bool moveLinkActive; + bool stateShareLoadActive; }; TransientSettings& getTransientSettings(); diff --git a/include/dusk/time.h b/include/dusk/time.h index 948a2fc171..c43437f639 100644 --- a/include/dusk/time.h +++ b/include/dusk/time.h @@ -1,9 +1,10 @@ #ifndef DUSK_TIME_H #define DUSK_TIME_H -#include -#include #include +#include + +#include "SDL3/SDL_timer.h" #ifdef _WIN32 #ifndef WIN32_LEAN_AND_MEAN @@ -15,28 +16,26 @@ #include #include #include -#else -#include "SDL3/SDL_timer.h" #endif class Limiter { - using delta_clock = std::chrono::high_resolution_clock; - using duration_t = std::chrono::nanoseconds; - public: - void Reset() { m_oldTime = delta_clock::now(); } + using duration_t = Uint64; + + void Reset() { m_oldTime = SDL_GetTicksNS(); } void Sleep(duration_t targetFrameTime) { - if (targetFrameTime.count() == 0) { + if (targetFrameTime == 0) { return; } - auto start = delta_clock::now(); + const Uint64 start = SDL_GetTicksNS(); duration_t adjustedSleepTime = SleepTime(targetFrameTime); - if (adjustedSleepTime.count() > 0) { + if (adjustedSleepTime > 0) { NanoSleep(adjustedSleepTime); - duration_t overslept = TimeSince(start) - adjustedSleepTime; - if (overslept < duration_t{targetFrameTime}) { + const duration_t elapsed = TimeSince(start); + const duration_t overslept = elapsed > adjustedSleepTime ? elapsed - adjustedSleepTime : 0; + if (overslept < targetFrameTime) { m_overheadTimes[m_overheadTimeIdx] = overslept; m_overheadTimeIdx = (m_overheadTimeIdx + 1) % m_overheadTimes.size(); } @@ -45,23 +44,23 @@ public: } duration_t SleepTime(duration_t targetFrameTime) { - const auto sleepTime = duration_t{targetFrameTime} - TimeSince(m_oldTime); - m_overhead = std::accumulate(m_overheadTimes.begin(), m_overheadTimes.end(), duration_t{}) / m_overheadTimes.size(); + const duration_t elapsed = TimeSince(m_oldTime); + const duration_t sleepTime = elapsed < targetFrameTime ? targetFrameTime - elapsed : 0; + m_overhead = std::accumulate(m_overheadTimes.begin(), m_overheadTimes.end(), duration_t{0}) / + m_overheadTimes.size(); if (sleepTime > m_overhead) { return sleepTime - m_overhead; } - return duration_t{0}; + return 0; } private: - delta_clock::time_point m_oldTime; + Uint64 m_oldTime = 0; std::array m_overheadTimes{}; size_t m_overheadTimeIdx = 0; - duration_t m_overhead = duration_t{0}; + duration_t m_overhead = 0; - duration_t TimeSince(delta_clock::time_point start) { - return std::chrono::duration_cast(delta_clock::now() - start); - } + duration_t TimeSince(Uint64 start) const { return SDL_GetTicksNS() - start; } #if _WIN32 void NanoSleep(const duration_t duration) { @@ -85,9 +84,10 @@ private: LARGE_INTEGER start, current; QueryPerformanceCounter(&start); - LONGLONG ticksToWait = static_cast(duration.count() * countPerNs); - if (DWORD ms = std::chrono::duration_cast(duration).count(); ms > 1) { - ::Sleep(ms - 1); + const LONGLONG ticksToWait = static_cast(duration * countPerNs); + const Uint64 ms = duration / 1'000'000ULL; + if (ms > 1) { + ::Sleep(static_cast(ms - 1)); } do { QueryPerformanceCounter(¤t); @@ -99,7 +99,7 @@ private: } while (current.QuadPart - start.QuadPart < ticksToWait); } #else - void NanoSleep(const duration_t duration) { SDL_DelayPrecise(duration.count()); } + void NanoSleep(const duration_t duration) { SDL_DelayPrecise(duration); } #endif }; diff --git a/include/dusk/version.hpp b/include/dusk/version.hpp new file mode 100644 index 0000000000..fe393b049b --- /dev/null +++ b/include/dusk/version.hpp @@ -0,0 +1,67 @@ +#ifndef DUSK_VERSION_HPP +#define DUSK_VERSION_HPP + +/** + * Functionality for switching game behavior based on the loaded game version (e.g. PAL/JPN, GC/Wii) + */ +namespace dusk::version { + enum class GameVersion : u8 { + GcnUsa = VERSION_GCN_USA, + GcnPal = VERSION_GCN_PAL, + GcnJpn = VERSION_GCN_JPN, + WiiUsaRev0 = VERSION_WII_USA_R0, + WiiUsa = VERSION_WII_USA_R2, + WiiPal = VERSION_WII_PAL, + WiiJpn = VERSION_WII_JPN, + WiiKor = VERSION_WII_KOR, + }; + + bool isGcn(); + bool isWii(); + bool isPalOrAtLeastWiiR2(); + + bool isRegionPal(); + bool isRegionJpn(); + bool isRegionUsa(); + + GameVersion getGameVersion(); + + const DVDDiskID& getDiskID(); + + void init(); + + template + struct VersionOption { + GameVersion mVersion; + T mValue; + + constexpr VersionOption(GameVersion version, T value) : mVersion(version), mValue(value) {} + }; + + template + const T& versionSelect(const std::initializer_list> options) { + const auto version = getGameVersion(); + for (const auto& opt : options) { + if (opt.mVersion == version) { + return opt.mValue; + } + } + + // Unable to find value. + abort(); + } + + template + const T& versionSelect(const std::initializer_list> options, const T& defaultValue) { + const auto version = getGameVersion(); + for (const auto& opt : options) { + if (opt.mVersion == version) { + return opt.mValue; + } + } + + return defaultValue; + } +} // namespace dusk::version + +#endif // DUSK_VERSION_HPP 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/global.h b/include/global.h index eda2a610ec..556c5f6207 100644 --- a/include/global.h +++ b/include/global.h @@ -73,6 +73,9 @@ #endif #ifndef __MWERKS__ +#ifdef __cplusplus +extern "C" { +#endif // Silence clangd errors about MWCC PPC intrinsics by declaring them here. extern int __cntlzw(unsigned int); extern int __rlwimi(int, int, int, int, int); @@ -80,7 +83,14 @@ extern void __dcbf(void*, int); extern void __dcbz(void*, int); extern void __sync(); extern int __abs(int); -void* __memcpy(void*, const void*, int); +#if defined(__has_builtin) && __has_builtin(__builtin_memcpy) +#define __memcpy __builtin_memcpy +#else +#define __memcpy memcpy +#endif +#ifdef __cplusplus +} +#endif #endif #ifndef M_PI @@ -220,12 +230,16 @@ using std::isnan; // Some basic macros that are more convenient than putting down #if blocks for one-line changes. #if TARGET_PC #define IF_DUSK(statement) statement +#define IF_DUSK_BLOCK(cond) if (cond) { +#define IF_DUSK_BLOCK_END } #define IF_DUSK_ARG(expr) , expr #define IF_NOT_DUSK(statement) #define DUSK_IF_ELSE(dusk, orig) dusk #else #define IF_DUSK(statement) #define IF_DUSK_ARG(expr) +#define IF_DUSK_BLOCK(cond) +#define IF_DUSK_BLOCK_END #define IF_NOT_DUSK(statement) statement #define DUSK_IF_ELSE(dusk, orig) orig #endif 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/JUTGamePad.h b/libs/JSystem/include/JSystem/JUtility/JUTGamePad.h index 30bf685700..45e7227fe3 100644 --- a/libs/JSystem/include/JSystem/JUtility/JUTGamePad.h +++ b/libs/JSystem/include/JSystem/JUtility/JUTGamePad.h @@ -263,6 +263,9 @@ public: /* 0x9C */ u8 field_0x9c[4]; /* 0xA0 */ OSTime mResetHoldStartTime; /* 0xA8 */ u8 field_0xa8; +#if TARGET_PC + u32 mResetHoldFrameCount; +#endif }; /** 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..b8054e2130 100644 --- a/libs/JSystem/src/JFramework/JFWDisplay.cpp +++ b/libs/JSystem/src/JFramework/JFWDisplay.cpp @@ -18,6 +18,7 @@ #include "dusk/logging.h" #include "dusk/settings.h" #include "dusk/time.h" +#include "f_op/f_op_overlap_mng.h" #include "SDL3/SDL_timer.h" #include "tracy/Tracy.hpp" @@ -205,14 +206,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 +217,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 @@ -383,22 +369,30 @@ constexpr auto FRAME_PERIOD = std::chrono::duration_cast(1001.0 / 30000.0)); constexpr auto RETRACE_PERIOD = FRAME_PERIOD / 2; -static void waitPrecise(Limiter& limiter, Uint64 targetNs) { - const auto sleepTime = limiter.SleepTime(std::chrono::nanoseconds(targetNs)); +static void waitPrecise(Limiter& limiter, Limiter::duration_t targetNs) { + const auto sleepTime = limiter.SleepTime(targetNs); dusk::frameUsagePct = - 100.0f * (1.0f - static_cast(sleepTime.count()) / static_cast(targetNs)); - limiter.Sleep(std::chrono::nanoseconds(targetNs)); + 100.0f * (1.0f - static_cast(sleepTime) / static_cast(targetNs)); + limiter.Sleep(targetNs); } #endif 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) { p1 = OS_TIMER_CLOCK / 120; } + + #if TARGET_PC + if (fopOvlpM_IsPeek() && dusk::getTransientSettings().stateShareLoadActive) { + return; + } + #endif + ZoneScopedC(tracy::Color::DimGray); #endif 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/JUTGamePad.cpp b/libs/JSystem/src/JUtility/JUTGamePad.cpp index 091cc382d1..2ddf4397dc 100644 --- a/libs/JSystem/src/JUtility/JUTGamePad.cpp +++ b/libs/JSystem/src/JUtility/JUTGamePad.cpp @@ -64,6 +64,9 @@ BOOL JUTGamePad::init() { void JUTGamePad::clear() { mButtonReset.mReset = false; field_0xa8 = 1; +#if TARGET_PC + mResetHoldFrameCount = 0; +#endif } PADStatus JUTGamePad::mPadStatus[4]; @@ -219,11 +222,19 @@ void JUTGamePad::update() { mButtonReset.mReset = false; } else if (!JUTGamePad::C3ButtonReset::sResetOccurred) { if (mButtonReset.mReset == true) { +#if TARGET_PC + checkResetCallback(++mResetHoldFrameCount * (OS_TIMER_CLOCK / 30)); +#else OSTime hold_time = OSGetTime() - mResetHoldStartTime; checkResetCallback(hold_time); +#endif } else { mButtonReset.mReset = true; +#if TARGET_PC + mResetHoldFrameCount = 0; +#else mResetHoldStartTime = OSGetTime(); +#endif } } 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/SSystem/SComponent/c_lib.cpp b/src/SSystem/SComponent/c_lib.cpp index 6441d6ceb8..d73dfb8627 100644 --- a/src/SSystem/SComponent/c_lib.cpp +++ b/src/SSystem/SComponent/c_lib.cpp @@ -468,9 +468,20 @@ s16 cLib_targetAngleX(cXyz const* lhs, cXyz const* rhs) { void cLib_offsetPos(cXyz* pdest, cXyz const* psrc, s16 angle, cXyz const* vec) { f32 cos = cM_scos(angle); f32 sin = cM_ssin(angle); + // MWCC loads vec members into registers before writing to pdest; other compilers may not, + // which corrupts results when pdest and vec alias the same memory. +#if !__MWERKS__ + f32 vx = vec->x; + f32 vy = vec->y; + f32 vz = vec->z; + pdest->x = psrc->x + (vx * cos + vz * sin); + pdest->y = psrc->y + vy; + pdest->z = psrc->z + (vz * cos - vx * sin); +#else pdest->x = psrc->x + (vec->x * cos + vec->z * sin); pdest->y = psrc->y + vec->y; pdest->z = psrc->z + (vec->z * cos - vec->x * sin); +#endif } /** diff --git a/src/Z2AudioLib/Z2WolfHowlMgr.cpp b/src/Z2AudioLib/Z2WolfHowlMgr.cpp index cb4f387a21..f12d453fbe 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 = 0.8909f; + 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..28c979bab4 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; @@ -5871,6 +5877,11 @@ void daAlink_c::setItemMatrix(int param_0) { modelCalc(mSheathModel); int var_r26; + + #if AVOID_UB + var_r26 = 0; + #endif + if (!checkNoResetFlg3(FLG3_UNK_4000000)) { if (mEquipItem == 0x103 || param_0 != 0) { mSwordModel->setBaseTRMtx(mpLinkModel->getAnmMtx(mLeftItemJntNo)); @@ -18930,11 +18941,20 @@ void daAlink_c::setDrawHand() { mpLinkHandModel->setBaseTRMtx(mpLinkModel->getBaseTRMtx()); mpLinkHandModel->calc(); +#if TARGET_PC + // FRAME INTERP NOTE: Always set these, otherwise the hands occasionally zip to origin. + // Doing it regardless of interpolation being active seems harmless. + mpLinkHandModel->setAnmMtx(1, mpLinkModel->getAnmMtx(9)); + mpLinkHandModel->setAnmMtx(2, mpLinkModel->getAnmMtx(0xE)); +#endif + if (var_r30 == 0xFE || var_r30 == 0xFB) { field_0x06d0 = field_0x06d8; } else { field_0x06d0 = mpLinkHandModel->getModelData()->getMaterialNodePointer(var_r30)->getShape(); +#if !TARGET_PC mpLinkHandModel->setAnmMtx(1, mpLinkModel->getAnmMtx(9)); +#endif } if (var_r30 == 0xFB) { @@ -18953,7 +18973,9 @@ void daAlink_c::setDrawHand() { field_0x06d4 = field_0x06dc; } else { field_0x06d4 = mpLinkHandModel->getModelData()->getMaterialNodePointer(var_r29)->getShape(); +#if !TARGET_PC mpLinkHandModel->setAnmMtx(2, mpLinkModel->getAnmMtx(0xE)); +#endif } if (var_r29 == 0xFB) { 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_demo.inc b/src/d/actor/d_a_alink_demo.inc index aa77b24129..5ac30a3cb9 100644 --- a/src/d/actor/d_a_alink_demo.inc +++ b/src/d/actor/d_a_alink_demo.inc @@ -23,6 +23,7 @@ #include "d/actor/d_a_npc_tkc.h" #include +#include "dusk/imgui/ImGuiConsole.hpp" #include "dusk/settings.h" BOOL daAlink_c::checkEventRun() const { @@ -4005,6 +4006,15 @@ int daAlink_c::procGanonFinishInit() { field_0x37c8 = current.pos; onEndResetFlg1(ERFLG1_SHIELD_BACKBONE); + +#if TARGET_PC + if (dusk::getSettings().game.speedrunMode) { + if (dusk::m_speedrunInfo.m_isRunStarted) { + dusk::m_speedrunInfo.stopRun(); + } + } +#endif + return 1; } diff --git a/src/d/actor/d_a_alink_dusk.cpp b/src/d/actor/d_a_alink_dusk.cpp index 9b937d3678..d53476aa91 100644 --- a/src/d/actor/d_a_alink_dusk.cpp +++ b/src/d/actor/d_a_alink_dusk.cpp @@ -41,7 +41,7 @@ void daAlink_c::handleWolfHowl() { return; } - bool canTransform = false; + bool canHowl = false; if (mLinkAcch.ChkGroundHit() && !checkModeFlg(MODE_PLAYER_FLY) && !checkMagneBootsOn()) { if (!checkForestOldCentury()) { @@ -52,12 +52,17 @@ void daAlink_c::handleWolfHowl() { (checkEventRun() || getMidnaActor()->checkMetamorphoseEnable()) && (checkModeFlg(4) || dComIfGp_checkPlayerStatus0(0, 0x10)))) { - canTransform = true; + canHowl = true; } } } } + if (!canHowl) { + Z2GetAudioMgr()->seStart(Z2SE_SYS_ERROR, NULL, 0, 0, 1.0f, 1.0f, -1.0f, -1.0f, 0); + return; + } + getWolfHowlMgrP()->setCorrectCurve(9); procWolfHowlDemoInit(); } @@ -143,15 +148,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 6bf9ae23dc..b278d866fc 100644 --- a/src/d/actor/d_a_alink_link.inc +++ b/src/d/actor/d_a_alink_link.inc @@ -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::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_alink_wolf.inc b/src/d/actor/d_a_alink_wolf.inc index 0a6da841c5..b2e60aea01 100644 --- a/src/d/actor/d_a_alink_wolf.inc +++ b/src/d/actor/d_a_alink_wolf.inc @@ -149,6 +149,23 @@ void daAlink_c::changeWolf() { mpLinkModel = initModel(static_cast(dComIfG_getObjectRes(l_wArcName, 14)), 0x20200); +#ifdef TARGET_PC + // Update Wolf Link's eye maxLOD to prevent the eyes from disappearing + { + J3DTexture* tex = mpLinkModel->getModelData()->getTexture(); + JUTNameTab* nametable = mpLinkModel->getModelData()->getTextureName(); + if (tex != nullptr && nametable != nullptr) { + for (u16 i = 0; i < tex->getNum(); i++) { + const char* tex_name = nametable->getName(i); + if (tex_name != NULL && strcmp(tex_name, "wl_eyeball") == 0) { + ResTIMG* timg = tex->getResTIMG(i); + timg->maxLOD = 0; + } + } + } + } +#endif + J3DModelData* chainModelData = static_cast(dComIfG_getObjectRes(l_wArcName, 15)); for (u16 i = 0; i < 4; i++) { mpWlChainModels[i] = initModel(chainModelData, 0); @@ -162,6 +179,23 @@ void daAlink_c::changeWolf() { mpWlMidnaHairModel = initModelEnv(static_cast(dComIfG_getObjectRes(l_wArcName, 11)), 0x1000000); +#ifdef TARGET_PC + // Update Midna's eye maxLOD to prevent the eyes from disappearing + { + J3DTexture* tex = mpWlMidnaModel->getModelData()->getTexture(); + JUTNameTab* nametable = mpWlMidnaModel->getModelData()->getTextureName(); + if (tex != nullptr && nametable != nullptr) { + for (u16 i = 0; i < tex->getNum(); i++) { + const char* tex_name = nametable->getName(i); + if (tex_name != NULL && strcmp(tex_name, "midona_eyeball") == 0) { + ResTIMG* timg = tex->getResTIMG(i); + timg->maxLOD = 0; + } + } + } + } +#endif + mpDMidnaBrk = static_cast(dComIfG_getObjectRes(l_wArcName, 18)); mpDMidnaBrk->searchUpdateMaterialID(mpWlMidnaModel->getModelData()); mpWlMidnaModel->getModelData()->entryTevRegAnimator(mpDMidnaBrk); @@ -342,6 +376,26 @@ void daAlink_c::changeLink(int param_0) { initModel(static_cast(dComIfG_getObjectRes(mArcName, "zl_face.bmd")), 0x20200); } +#ifdef TARGET_PC + // Update Adult Link's eye maxLOD to prevent the eyes from disappearing + { + J3DTexture* tex = mpLinkFaceModel->getModelData()->getTexture(); + JUTNameTab* nametable = mpLinkFaceModel->getModelData()->getTextureName(); + if (tex != nullptr && nametable != nullptr) { + for (u16 i = 0; i < tex->getNum(); i++) { + const char* tex_name = nametable->getName(i); + if (tex_name != nullptr && + (strcmp(tex_name, "al_eyeball") == 0 || strcmp(tex_name, "highlight02") == 0 || + strcmp(tex_name, "eye_kage01") == 0)) + { + ResTIMG* timg = tex->getResTIMG(i); + timg->maxLOD = 0; + } + } + } + } +#endif + modelData = static_cast(dComIfG_getObjectRes(mArcName, "al_bootsH.bmd")); u16 i; for (i = 0; i < 2; i++) { diff --git a/src/d/actor/d_a_b_bq.cpp b/src/d/actor/d_a_b_bq.cpp index 70ab84705b..637e82360e 100644 --- a/src/d/actor/d_a_b_bq.cpp +++ b/src/d/actor/d_a_b_bq.cpp @@ -2059,7 +2059,15 @@ static void demo_camera(b_bq_class* i_this) { for (int i = 0; i < 5; i++) { static u16 g_e_i[] = {0x83EB, 0x83EC, 0x83ED, 0x83EE, 0x83EF}; - dComIfGp_particle_set(g_e_i[i], &pos, NULL, NULL); + #if TARGET_PC + if (i == 0) { + static const cXyz effWideScale = {mDoGph_gInf_c::getAspect(), 1.0f, 1.0f}; + dComIfGp_particle_set(g_e_i[i], &pos, NULL, &effWideScale); + } else + #endif + { + dComIfGp_particle_set(g_e_i[i], &pos, NULL, NULL); + } } i_this->mSound.startCreatureSound(Z2SE_EN_BOSS_CONVERGE, 0, 0); diff --git a/src/d/actor/d_a_b_gnd.cpp b/src/d/actor/d_a_b_gnd.cpp index 2272c35376..cd6bce78c4 100644 --- a/src/d/actor/d_a_b_gnd.cpp +++ b/src/d/actor/d_a_b_gnd.cpp @@ -17,6 +17,9 @@ #include "Z2AudioLib/Z2Instances.h" +#include "dusk/frame_interpolation.h" +#include "dusk/settings.h" + class daB_GND_HIO_c : public JORReflexible { public: daB_GND_HIO_c(); @@ -279,6 +282,30 @@ static int h_nodeCallBack(J3DJoint* i_joint, int param_2) { return 1; } +#if TARGET_PC +static void b_gnd_rein_interp_callback(bool isSimFrame, void* pUserWork) { + b_gnd_class* i_this = (b_gnd_class*)pUserWork; + if (!i_this->mReinsInterpPrevValid || !i_this->mReinsInterpCurrValid) { + return; + } + const f32 alpha = dusk::frame_interp::get_interpolation_step(); + for (int r = 0; r < 2; r++) { + cXyz* dst = i_this->mHorseReins[r].getPos(0); + for (int i = 0; i < 16; i++) { + const cXyz& p0 = i_this->mReinsInterpPrev[r][i]; + const cXyz& p1 = i_this->mReinsInterpCurr[r][i]; + dst[i] = p0 + (p1 - p0) * alpha; + } + } + cXyz* dst = i_this->field_0x21e8.getPos(0); + for (int i = 0; i < 2; i++) { + const cXyz& p0 = i_this->mReinsTexInterpPrev[i]; + const cXyz& p1 = i_this->mReinsTexInterpCurr[i]; + dst[i] = p0 + (p1 - p0) * alpha; + } +} +#endif + static int daB_GND_Draw(b_gnd_class* i_this) { fopAc_ac_c* a_this = (fopAc_ac_c*)i_this; @@ -366,6 +393,21 @@ static int daB_GND_Draw(b_gnd_class* i_this) { i_this->field_0x21e8.update(2, l_color, &a_this->tevStr); dComIfGd_set3DlineMat(&i_this->field_0x21e8); +#if TARGET_PC + if (dusk::getSettings().game.enableFrameInterpolation) { + if (i_this->mReinsInterpCurrValid) { + memcpy(i_this->mReinsInterpPrev, i_this->mReinsInterpCurr, sizeof(i_this->mReinsInterpCurr)); + memcpy(i_this->mReinsTexInterpPrev, i_this->mReinsTexInterpCurr, sizeof(i_this->mReinsTexInterpCurr)); + i_this->mReinsInterpPrevValid = true; + } + for (int r = 0; r < 2; r++) { + memcpy(i_this->mReinsInterpCurr[r], i_this->mHorseReins[r].getPos(0), 16 * sizeof(cXyz)); + } + memcpy(i_this->mReinsTexInterpCurr, i_this->field_0x21e8.getPos(0), 2 * sizeof(cXyz)); + i_this->mReinsInterpCurrValid = true; + dusk::frame_interp::add_interpolation_callback(&b_gnd_rein_interp_callback, i_this); + } +#endif } return 1; @@ -1189,10 +1231,16 @@ static void b_gnd_h_end(b_gnd_class* i_this) { if (i_this->mDemoCamMode < 32) { i_this->mDemoCamMode = 32; +#if TARGET_PC + i_this->mDemoCamSyncTicks = 2; +#endif } else { i_this->mDemoCamMode = 34; i_this->mDemoCamTimer = 0; i_this->mMoveMode = 2; +#if TARGET_PC + i_this->mDemoCamSyncTicks = 2; +#endif } } break; @@ -2887,6 +2935,9 @@ static void demo_camera(b_gnd_class* i_this) { cXyz spF0; s8 sp8 = false; +#if TARGET_PC + const s16 entry_demo_cam_mode = i_this->mDemoCamMode; +#endif switch (i_this->mDemoCamMode) { case 0: break; @@ -3725,6 +3776,15 @@ static void demo_camera(b_gnd_class* i_this) { i_this->mDemoCamTimer = 10000; } } +#if TARGET_PC + if (entry_demo_cam_mode != i_this->mDemoCamMode) { + i_this->mDemoCamSyncTicks = 2; + } + if (i_this->mDemoCamSyncTicks > 0) { + dusk::frame_interp::request_presentation_sync(); + i_this->mDemoCamSyncTicks--; + } +#endif } static void anm_se_set(b_gnd_class* i_this) { diff --git a/src/d/actor/d_a_b_ob.cpp b/src/d/actor/d_a_b_ob.cpp index 5622375fec..f7aa9b1f0b 100644 --- a/src/d/actor/d_a_b_ob.cpp +++ b/src/d/actor/d_a_b_ob.cpp @@ -2725,7 +2725,16 @@ static void demo_camera(b_ob_class* i_this) { for (int i = 0; i < 5; i++) { static u16 ex_eff[] = {dPa_RM(ID_ZI_S_OI_CONVERGE_FILTER), dPa_RM(ID_ZI_S_OI_CONVERGE_FILTEROUT), dPa_RM(ID_ZI_S_OI_CONVERGE_HIDE), dPa_RM(ID_ZI_S_OI_CONVERGE_POLYGON_A), dPa_RM(ID_ZI_S_OI_CONVERGE_POLYGON_B)}; - dComIfGp_particle_set(ex_eff[i], &room_pos, NULL, &sc); + + #if TARGET_PC + if (i == 0) { + static const cXyz effWideScale = {mDoGph_gInf_c::getAspect() * 10.0f, 10.0f, 10.0f}; + dComIfGp_particle_set(ex_eff[i], &room_pos, NULL, &effWideScale); + } else + #endif + { + dComIfGp_particle_set(ex_eff[i], &room_pos, NULL, &sc); + } } i_this->mDemoCamEye.set(-4820.0f, -18600.0f, -510.0f); diff --git a/src/d/actor/d_a_balloon_2D.cpp b/src/d/actor/d_a_balloon_2D.cpp index 6d9c66b464..8627505371 100644 --- a/src/d/actor/d_a_balloon_2D.cpp +++ b/src/d/actor/d_a_balloon_2D.cpp @@ -6,6 +6,7 @@ #include "d/dolzel_rel.h" // IWYU pragma: keep #include "d/actor/d_a_balloon_2D.h" +#include "dusk/frame_interpolation.h" #include "JSystem/J2DGraph/J2DGrafContext.h" #include "JSystem/J2DGraph/J2DScreen.h" #include "JSystem/J2DGraph/J2DTextBox.h" @@ -318,11 +319,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; @@ -442,7 +439,12 @@ void daBalloon2D_c::setComboAlpha() { void daBalloon2D_c::drawAddScore() { for (s32 i = 19; i >= 0; i--) { if (field_0x5f8[i].field_0xe != 0) { - field_0x5f8[i].field_0xe--; +#ifdef TARGET_PC + if (dusk::frame_interp::get_ui_tick_pending()) +#endif + { + field_0x5f8[i].field_0xe--; + } s32 score3; s32 score2; s32 score = field_0x5f8[i].field_0xc; @@ -450,8 +452,13 @@ void daBalloon2D_c::drawAddScore() { u8 local_88 = 0xff; f32 dVar11 = 30.0f; f32 dVar9 = 30.0f; - field_0x5f8[i].field_0x0.x += cM_ssin(temp0) * 0.3f; - field_0x5f8[i].field_0x0.y -= 1.0f; +#ifdef TARGET_PC + if (dusk::frame_interp::get_ui_tick_pending()) +#endif + { + field_0x5f8[i].field_0x0.x += cM_ssin(temp0) * 0.3f; + field_0x5f8[i].field_0x0.y -= 1.0f; + } if (field_0x5f8[i].field_0xe < 10) { f32 fVar5 = field_0x5f8[i].field_0xe / 10.0f; local_88 = fVar5 * 255.0f; 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 fc122cd4e3..264f559e07 100644 --- a/src/d/actor/d_a_dshutter.cpp +++ b/src/d/actor/d_a_dshutter.cpp @@ -215,15 +215,14 @@ int daDsh_c::create() { mType = getType(); -#ifdef TARGET_PC - const char* l_resName[] = {l_arcName[mType], ""}; -#else - // !@bug By making this static, it is only initialized the first time it runs - // If gate types that use other arcs are loaded later (without reloading the code) - // this array never gets updated and will load the incorrect arc - // On GC/Wii, REL loading causes this to reset/reinitialize so the bug is avoided - // but TPHD is all statically linked so daDsh_c::CreateHeap fails to get model data and the gate unloads + // !@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); 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_fm.cpp b/src/d/actor/d_a_e_fm.cpp index ca01f73c29..81a1b404f0 100644 --- a/src/d/actor/d_a_e_fm.cpp +++ b/src/d/actor/d_a_e_fm.cpp @@ -1677,7 +1677,16 @@ static void demo_camera(e_fm_class* i_this) { cXyz spBC(0.0f, 0.0f, 0.0f); for (int i = 0; i < 4; i++) { static u16 g_e_i[] = {0x847B, 0x847C, 0x847D, 0x847E}; - dComIfGp_particle_set(g_e_i[i], &spBC, NULL, NULL); + + #if TARGET_PC + if (i == 0) { + static const cXyz effWideScale = {mDoGph_gInf_c::getAspect(), 1.0f, 1.0f}; + dComIfGp_particle_set(g_e_i[i], &spBC, NULL, &effWideScale); + } else + #endif + { + dComIfGp_particle_set(g_e_i[i], &spBC, NULL, NULL); + } } i_this->mDemoCamFovy = 55.0f + NREG_F(10); 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_mb.cpp b/src/d/actor/d_a_e_mb.cpp index 939b253475..aa77a99612 100644 --- a/src/d/actor/d_a_e_mb.cpp +++ b/src/d/actor/d_a_e_mb.cpp @@ -12,6 +12,8 @@ #include "d/d_bomb.h" #include "c/c_damagereaction.h" #include "Z2AudioLib/Z2Instances.h" +#include "dusk/frame_interpolation.h" +#include "dusk/settings.h" #define ACTION_STANDBY 0 #define ACTION_WALK1 1 @@ -63,6 +65,22 @@ static void anm_init(e_mb_class* i_this, int i_anmID, f32 i_morf, u8 i_attr, f32 i_this->mAnm = i_anmID; } +#if TARGET_PC +static void e_mb_rope_interp_callback(bool isSimFrame, void* pUserWork) { + e_mb_class* i_this = (e_mb_class*)pUserWork; + if (!i_this->mRopeInterpPrevValid || !i_this->mRopeInterpCurrValid) { + return; + } + const f32 alpha = dusk::frame_interp::get_interpolation_step(); + cXyz* dst = i_this->mRopeMat.getPos(0); + for (int i = 0; i < 16; i++) { + const cXyz& p0 = i_this->mRopeInterpPrev[i]; + const cXyz& p1 = i_this->mRopeInterpCurr[i]; + dst[i] = p0 + (p1 - p0) * alpha; + } +} +#endif + static int daE_MB_Draw(e_mb_class* i_this) { fopAc_ac_c* a_this = (fopAc_ac_c*)i_this; @@ -86,6 +104,17 @@ static int daE_MB_Draw(e_mb_class* i_this) { static GXColor l_color = {0x14, 0x0F, 0x00, 0xFF}; i_this->mRopeMat.update(16, l_color, &a_this->tevStr); dComIfGd_set3DlineMat(&i_this->mRopeMat); +#if TARGET_PC + if (dusk::getSettings().game.enableFrameInterpolation) { + if (i_this->mRopeInterpCurrValid) { + memcpy(i_this->mRopeInterpPrev, i_this->mRopeInterpCurr, sizeof(i_this->mRopeInterpCurr)); + i_this->mRopeInterpPrevValid = true; + } + memcpy(i_this->mRopeInterpCurr, i_this->mRopeMat.getPos(0), 16 * sizeof(cXyz)); + i_this->mRopeInterpCurrValid = true; + dusk::frame_interp::add_interpolation_callback(&e_mb_rope_interp_callback, i_this); + } +#endif return 1; } 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_sm2.cpp b/src/d/actor/d_a_e_sm2.cpp index fdbc338670..f55c4411cb 100644 --- a/src/d/actor/d_a_e_sm2.cpp +++ b/src/d/actor/d_a_e_sm2.cpp @@ -14,6 +14,10 @@ #include "f_op/f_op_camera_mng.h" #include +#if TARGET_PC +#include "dusk/frame_interpolation.h" +#endif + class daE_SM2_HIO_c : public fOpAcm_HIO_entry_c { public: daE_SM2_HIO_c(); @@ -76,8 +80,62 @@ static int nodeCallBack(J3DJoint* i_joint, int param_1) { return 1; } +#if TARGET_PC +static void daE_SM2_interp_callback(bool isSimFrame, void* pUserWork) { + e_sm2_class* i_this = static_cast(pUserWork); + if (i_this == NULL) { + return; + } + + fopAc_ac_c* actor = (fopAc_ac_c*)&i_this->enemy; + g_env_light.settingTevStruct(0, &actor->current.pos, &actor->tevStr); + + if (!i_this->isPiece) { + if (i_this->modelMorf == NULL) { + return; + } + J3DModel* model = i_this->modelMorf->getModel(); + if (model == NULL) { + return; + } + g_env_light.setLightTevColorType_MAJI(model, &actor->tevStr); + + J3DMaterial* material = model->getModelData()->getMaterialNodePointer(0); + material->getTevKColor(1)->r = i_this->color_R; + material->getTevKColor(1)->g = i_this->color_G; + material->getTevKColor(1)->b = i_this->color_B; + material->getTevKColor(1)->a = 217.0f * i_this->color_alpha; + + if (i_this->pbtk != NULL) { + i_this->pbtk->entry(model->getModelData()); + } + } else { + if (i_this->pieceModelMorf == NULL) { + return; + } + J3DModel* model = i_this->pieceModelMorf->getModel(); + if (model == NULL) { + return; + } + + J3DMaterial* material = model->getModelData()->getMaterialNodePointer(0); + material->getTevKColor(1)->r = i_this->color_R; + material->getTevKColor(1)->g = i_this->color_G; + material->getTevKColor(1)->b = i_this->color_B; + material->getTevKColor(1)->a = 217.0f * i_this->color_alpha; + + g_env_light.setLightTevColorType_MAJI(model, &actor->tevStr); + } +} +#endif + static int daE_SM2_Draw(e_sm2_class* i_this) { fopAc_ac_c* actor = (fopAc_ac_c*)&i_this->enemy; + +#if TARGET_PC + dusk::frame_interp::add_interpolation_callback(&daE_SM2_interp_callback, i_this); +#endif + g_env_light.settingTevStruct(0, &actor->current.pos, &actor->tevStr); J3DModel* model; 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_e_ym.cpp b/src/d/actor/d_a_e_ym.cpp index 758bec6269..3f6bef6788 100644 --- a/src/d/actor/d_a_e_ym.cpp +++ b/src/d/actor/d_a_e_ym.cpp @@ -1069,7 +1069,7 @@ void daE_YM_c::executeDown() { if (mAcch.ChkGroundHit()) { if (mFlyType != 1) { #if TARGET_PC - bckSet(6, 0, 0.0f, dusk::getSettings().game.fastTears && dComIfGp_event_getMode() == 0 ? 2.0f : 1.0f); + bckSet(6, 0, 0.0f, dusk::getSettings().game.fastTears ? 2.0f : 1.0f); #else bckSet(6, 0, 0.0f, 1.0f); #endif @@ -1093,7 +1093,7 @@ void daE_YM_c::executeDown() { mSound.startCreatureSound(Z2SE_CM_BODYFALL_WATER_M, 0, -1); mSound.startCreatureSound(Z2SE_EN_YM_MOGAKU, 0, -1); #if TARGET_PC - bckSet(6, 0, 0.0f, dusk::getSettings().game.fastTears && dComIfGp_event_getMode() == 0 ? 2.0f : 1.0f); + bckSet(6, 0, 0.0f, dusk::getSettings().game.fastTears ? 2.0f : 1.0f); #else bckSet(6, 0, 0.0f, 1.0f); #endif @@ -1115,7 +1115,7 @@ void daE_YM_c::executeDown() { || dComIfG_Bgsp().GetGroundCode(gnd_chk) == 5) { #if TARGET_PC - bckSet(6, 0, 0.0f, dusk::getSettings().game.fastTears && dComIfGp_event_getMode() == 0 ? 2.0f : 1.0f); + bckSet(6, 0, 0.0f, dusk::getSettings().game.fastTears ? 2.0f : 1.0f); #else bckSet(6, 0, 0.0f, 1.0f); #endif diff --git a/src/d/actor/d_a_mant.cpp b/src/d/actor/d_a_mant.cpp index bcd7f05986..6a559c0e1c 100644 --- a/src/d/actor/d_a_mant.cpp +++ b/src/d/actor/d_a_mant.cpp @@ -12,9 +12,13 @@ #if TARGET_PC #include "dusk/dvd_asset.hpp" -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; } +#include "dusk/frame_interpolation.h" + +using GameVersion = dusk::version::GameVersion; + +static u8* l_Egnd_mantTEX_get() { alignas(32) static u8 buf[0x4000]; static bool _ = (dusk::LoadRelAsset(buf, "/rel/Final/Release/d_a_mant.rel", {{GameVersion::GcnUsa, 0x1C00}, {GameVersion::GcnPal, 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", {{GameVersion::GcnUsa, 0x5C00}, {GameVersion::GcnPal, 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", {{GameVersion::GcnUsa, 0x9C00}, {GameVersion::GcnPal, 0x9C00}}, 0x60), true); return buf; } #define l_Egnd_mantTEX (l_Egnd_mantTEX_get()) #define l_Egnd_mantTEX_U (l_Egnd_mantTEX_U_get()) #define l_Egnd_mantPAL (l_Egnd_mantPAL_get()) @@ -250,7 +254,9 @@ static u32 l_texCoord[338] = { }; #if TARGET_PC -static u8* l_Egnd_mantDL_get() { alignas(32) static u8 buf[0x3EC]; static bool _ = (dusk::LoadRelAsset(buf, "/rel/Final/Release/d_a_mant.rel", 0xA9A0, 0x3EC), true); return buf; } +using GameVersion = dusk::version::GameVersion; + +static u8* l_Egnd_mantDL_get() { alignas(32) static u8 buf[0x3EC]; static bool _ = (dusk::LoadRelAsset(buf, "/rel/Final/Release/d_a_mant.rel", {{GameVersion::GcnUsa, 0xA9A0}, {GameVersion::GcnPal, 0xA9A0}}, 0x3EC), true); return buf; } #define l_Egnd_mantDL (l_Egnd_mantDL_get()) #else #include "assets/l_Egnd_mantDL.h" @@ -267,6 +273,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 +327,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 +430,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 +453,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 +471,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 +748,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 +762,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_fish.cpp b/src/d/actor/d_a_mg_fish.cpp index 90781345ad..6791bcb61c 100644 --- a/src/d/actor/d_a_mg_fish.cpp +++ b/src/d/actor/d_a_mg_fish.cpp @@ -22,6 +22,7 @@ #include "d/d_s_play.h" #include "d/d_vibration.h" #include "f_op/f_op_kankyo_mng.h" +#include "dusk/version.hpp" #include #define ANM_MG_FISH_MOUTH_CLOSE 4 @@ -3230,7 +3231,17 @@ static int daMg_Fish_Execute(mg_fish_class* i_this) { daPy_py_c* player = daPy_getPlayerActorClass(); -#if VERSION == VERSION_GCN_JPN +#if TARGET_PC + if (dusk::version::isRegionJpn()) { + lit_1008 = 0; + } else if (dusk::version::isRegionPal()) { + if (dComIfGs_getPalLanguage() == dSv_player_config_c::LANGUAGE_ENGLISH) { + lit_1008 = 2; + } else { + lit_1008 = 0; + } + } +#elif VERSION == VERSION_GCN_JPN lit_1008 = 0; #elif VERSION == VERSION_GCN_PAL if (dComIfGs_getPalLanguage() == dSv_player_config_c::LANGUAGE_ENGLISH) { @@ -3843,7 +3854,19 @@ static int daMg_Fish_Create(fopAc_ac_c* i_this) { a_this->mResName = "O_gD_bott"; } -#if VERSION == VERSION_GCN_JPN +#if TARGET_PC + if (dusk::version::isRegionJpn()) { + lit_1008 = 0; + } else if (dusk::version::isRegionPal()) { + if (dComIfGs_getPalLanguage() == dSv_player_config_c::LANGUAGE_ENGLISH) { + lit_1008 = 2; + } else { + lit_1008 = 0; + } + } else { + lit_1008 = 1; + } +#elif VERSION == VERSION_GCN_JPN lit_1008 = 0; #elif VERSION == VERSION_GCN_PAL if (dComIfGs_getPalLanguage() == dSv_player_config_c::LANGUAGE_ENGLISH) { diff --git a/src/d/actor/d_a_mg_fshop.cpp b/src/d/actor/d_a_mg_fshop.cpp index 1c93648eac..f0905efd5d 100644 --- a/src/d/actor/d_a_mg_fshop.cpp +++ b/src/d/actor/d_a_mg_fshop.cpp @@ -5,14 +5,15 @@ #include "d/dolzel_rel.h" // IWYU pragma: keep +#include "Z2AudioLib/Z2Instances.h" +#include "d/actor/d_a_mg_fish.h" #include "d/actor/d_a_mg_fshop.h" #include "d/actor/d_a_npc_henna.h" -#include "d/actor/d_a_mg_fish.h" #include "d/actor/d_a_player.h" -#include "f_op/f_op_camera_mng.h" -#include "d/d_timer.h" #include "d/d_s_play.h" -#include "Z2AudioLib/Z2Instances.h" +#include "d/d_timer.h" +#include "dusk/version.hpp" +#include "f_op/f_op_camera_mng.h" #if TARGET_PC #include "dusk/gyro.h" @@ -761,6 +762,11 @@ static void koro2_game(fshop_class* i_this) { 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; @@ -782,20 +788,15 @@ static void koro2_game(fshop_class* i_this) { reg_f30 = 0.0f; } + s16 gyro_ax = 0; + s16 gyro_az = 0; #if TARGET_PC if (dusk::getSettings().game.enableGyroRollgoal) { - s16 rg_add_x; - s16 rg_add_z; - dusk::gyro::rollgoalTableOffset(rg_add_x, rg_add_z); - s16 tgt_x = static_cast(reg_f30 * (-6000.0f + JREG_F(7))) + rg_add_x; - s16 tgt_z = static_cast(reg_f31 * (-6000.0f + JREG_F(8))) + rg_add_z; - cLib_addCalcAngleS2(&i_this->field_0x4020.x, tgt_x, 4, 0x200); - cLib_addCalcAngleS2(&i_this->field_0x4020.z, tgt_z, 4, 0x200); + dusk::gyro::rollgoalTableOffset(gyro_ax, gyro_az); } -#else - 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); #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) { @@ -1742,7 +1743,18 @@ static int daFshop_Create(fopAc_ac_c* actor) { fopAcM_createChild(fpcNm_FSHOP_e, fopAcM_GetID(actor), 0xFFFFFF23, &actor->current.pos, fopAcM_GetRoomNo(actor), NULL, NULL, -1, NULL); u8 sp10; -#if VERSION == VERSION_GCN_PAL || VERSION == VERSION_WII_PAL +#if TARGET_PC + if (dusk::version::isRegionPal()) { + if (dComIfGs_getPalLanguage() == dSv_player_config_c::LANGUAGE_ENGLISH) { + sp10 = 2; + } else { + sp10 = 0; + } + } else { + sp10 = 1; + } + +#elif VERSION == VERSION_GCN_PAL || VERSION == VERSION_WII_PAL if (dComIfGs_getPalLanguage() == dSv_player_config_c::LANGUAGE_ENGLISH) { sp10 = 2; } else { diff --git a/src/d/actor/d_a_mg_rod.cpp b/src/d/actor/d_a_mg_rod.cpp index 96ad7aa148..956a9e3cff 100644 --- a/src/d/actor/d_a_mg_rod.cpp +++ b/src/d/actor/d_a_mg_rod.cpp @@ -25,6 +25,8 @@ #include #include +#include "dusk/version.hpp" + class dmg_rod_HIO_c : public JORReflexible { public: dmg_rod_HIO_c(); @@ -5734,10 +5736,22 @@ static void play_camera_u(dmg_rod_class* i_this) { static int dmg_rod_Execute(dmg_rod_class* i_this) { fopAc_ac_c* actor = &i_this->actor; + #if TARGET_PC + if (dusk::version::isPalOrAtLeastWiiR2()) { + if (dComIfGs_getPalLanguage() == 0) { + data_804BBBD4 = 2; + } else { + data_804BBBD4 = 0; + } + } else if (dusk::version::isRegionJpn()) { + data_804BBBD4 = 0; + } else { + data_804BBBD4 = 1; + } //TODO: It seems possible that dComIfGs_getPalLanguage returns a constant value for non-PAL // versions (causing the first block to be elided), and it's also possible that the value // being compared against is an enum value with per-version definitions. - #if VERSION == VERSION_SHIELD_DEBUG + #elif VERSION == VERSION_SHIELD_DEBUG if (dComIfGs_getPalLanguage() == 1) { data_804BBBD4 = 2; } else { @@ -6303,7 +6317,19 @@ static int dmg_rod_Create(fopAc_ac_c* i_this) { heap_size = 0xC9A0; } - #if VERSION == VERSION_SHIELD_DEBUG + #if TARGET_PC + if (dusk::version::isPalOrAtLeastWiiR2()) { + if (dComIfGs_getPalLanguage() == 0) { + data_804BBBD4 = 2; + } else { + data_804BBBD4 = 0; + } + } else if (dusk::version::isRegionJpn()) { + data_804BBBD4 = 0; + } else { + data_804BBBD4 = 1; + } + #elif VERSION == VERSION_SHIELD_DEBUG if (dComIfGs_getPalLanguage() == 1) { data_804BBBD4 = 2; } else { 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..86ef4314f3 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() { @@ -581,6 +592,13 @@ int daMirror_c::execute() { return 1; } +#if TARGET_PC + if (mPacket.mbReset) { + mPacket.mModelCount = 0; + mPacket.mbReset = false; + } +#endif + daPy_py_c* player = daPy_getLinkPlayerActorClass(); JUT_ASSERT(0, player != NULL); diff --git a/src/d/actor/d_a_movie_player.cpp b/src/d/actor/d_a_movie_player.cpp index c3074ac877..1f8bdeecd7 100644 --- a/src/d/actor/d_a_movie_player.cpp +++ b/src/d/actor/d_a_movie_player.cpp @@ -2855,7 +2855,7 @@ void* daMP_Reader(void*) { #endif } -static u8 daMP_ReadThreadStack[0x2000]; +static u8 daMP_ReadThreadStack[DUSK_IF_ELSE(8, 0x2000)]; #if TARGET_PC static BOOL VideoThreadCancelled; @@ -2880,7 +2880,7 @@ static BOOL daMP_CreateReadThread(s32 param_0) { static OSThread daMP_VideoDecodeThread; -static u8 daMP_VideoDecodeThreadStack[0x64000]; +static u8 daMP_VideoDecodeThreadStack[DUSK_IF_ELSE(8, 0x64000)]; static OSMessageQueue daMP_FreeTextureSetQueue; @@ -3132,7 +3132,7 @@ static BOOL AudioThreadCancelled; static OSThread daMP_AudioDecodeThread; -static u8 daMP_AudioDecodeThreadStack[0x64000]; +static u8 daMP_AudioDecodeThreadStack[DUSK_IF_ELSE(8, 0x64000)]; static OSMessageQueue daMP_FreeAudioBufferQueue; @@ -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_balloon.cpp b/src/d/actor/d_a_obj_balloon.cpp index 41430f97f0..4425483bef 100644 --- a/src/d/actor/d_a_obj_balloon.cpp +++ b/src/d/actor/d_a_obj_balloon.cpp @@ -205,6 +205,13 @@ int daObj_Balloon_c::_delete() { Z2GetAudioMgr()->seStop(Z2SE_OBJ_WATERMILL_ROUND, 0); if (mHIOInit) { hio_set = false; +#ifdef TARGET_PC + // !@bug d_a_obj_balloon.rel unload used to zero these file-statics; with static linking they dangle across scenes. + m_combo_type = 0xFFFFFFFF; + m_combo_count = 0; + m_combo_next_score = 0; + m_balloon_score = 0; +#endif } return 1; } 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_drop.cpp b/src/d/actor/d_a_obj_drop.cpp index 275a3d6e12..18d27840f7 100644 --- a/src/d/actor/d_a_obj_drop.cpp +++ b/src/d/actor/d_a_obj_drop.cpp @@ -265,8 +265,8 @@ int daObjDrop_c::modeParentWait() { mModeAction = 1; #if TARGET_PC - mModeTimer = dusk::getSettings().game.fastTears && dComIfGp_event_getMode() == 0 ? 20 : 40; - if (dusk::getSettings().game.fastTears && dComIfGp_event_getMode() == 0) { + mModeTimer = dusk::getSettings().game.fastTears ? 0 : 40; + if (dusk::getSettings().game.fastTears) { current.pos.y += 100.0f; } else { current.pos.y += 300.0f; @@ -285,7 +285,7 @@ int daObjDrop_c::modeParentWait() { case 2: createBodyEffect(); #if TARGET_PC - mModeTimer = dusk::getSettings().game.fastTears && dComIfGp_event_getMode() == 0 ? 5 : 45; + mModeTimer = dusk::getSettings().game.fastTears ? 0 : 45; #else mModeTimer = 45; #endif @@ -331,7 +331,7 @@ int daObjDrop_c::modeWait() { case 2: case 50: #if TARGET_PC - if (dusk::getSettings().game.fastTears && dComIfGp_event_getMode() == 0) { + if (dusk::getSettings().game.fastTears) { f32 player_dist = current.pos.abs(daPy_getPlayerActorClass()->current.pos); f32 home_dist = current.pos.abs(home.pos); 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_item.cpp b/src/d/actor/d_a_obj_item.cpp index 0db3ac3a55..e45de1933d 100644 --- a/src/d/actor/d_a_obj_item.cpp +++ b/src/d/actor/d_a_obj_item.cpp @@ -16,6 +16,10 @@ #include "f_op/f_op_camera_mng.h" #include "m_Do/m_Do_mtx.h" +#if TARGET_PC +#include "dusk/frame_interpolation.h" +#endif + static f32 Reflect(cXyz* i_vec, cBgS_PolyInfo const& i_polyinfo, f32 i_scale) { cM3dGPla plane; @@ -31,6 +35,27 @@ static f32 Reflect(cXyz* i_vec, cBgS_PolyInfo const& i_polyinfo, f32 i_scale) { return 0.0f; } +#if TARGET_PC +static void d_a_obj_item_interp_callback(bool isSimFrame, void* pUserWork) { + daItem_c* item = static_cast(pUserWork); + if (item == NULL || item->mpModel == NULL || !item->chkDraw()) { + return; + } + item->setTevStr(); + if (item->mpBrkAnm != NULL) { + s8 tevFrm = item->getTevFrm(); + if (tevFrm != -1) { + item->mpBrkAnm->entry(item->mpModel->getModelData(), tevFrm); + } else { + item->mpBrkAnm->entry(item->mpModel->getModelData()); + } + } + if (item->chkFlag(4)) { + fopAcM_setEffectMtx(item, item->mpModel->getModelData()); + } +} +#endif + const daItemBase_data& daItemBase_c::getData() { return m_data; } @@ -353,6 +378,10 @@ int daItem_c::_daItem_draw() { return 1; } +#if TARGET_PC + dusk::frame_interp::add_interpolation_callback(&d_a_obj_item_interp_callback, this); +#endif + if (chkDraw()) { return DrawBase(); } @@ -390,6 +419,9 @@ void daItem_c::procMainNormal() { cLib_chaseF(&scale.z, mItemScale.z, step_z); } + #if TARGET_PC + if (!dusk::getSettings().game.enableIndefiniteItemDrops) { + #endif if (mWaitTimer == 0) { if (mDisappearTimer == 0) { deleteItem(); @@ -399,6 +431,9 @@ void daItem_c::procMainNormal() { changeDraw(); } } + #if TARGET_PC + } + #endif mCcCyl.SetC(current.pos); dComIfG_Ccsp()->Set(&mCcCyl); @@ -1058,9 +1093,16 @@ int daItem_c::CountTimer() { if (checkCountTimer()) { if (mWaitTimer > 0) { mWaitTimer--; - } else if (mDisappearTimer > 0) { + } + #if TARGET_PC + else if (!dusk::getSettings().game.enableIndefiniteItemDrops && mDisappearTimer > 0) { mDisappearTimer--; } + #else + else if (mDisappearTimer > 0) { + mDisappearTimer--; + } + #endif } cLib_calcTimer(&mBoomWindTgTimer); 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_lv8OptiLift.cpp b/src/d/actor/d_a_obj_lv8OptiLift.cpp index a8610989a7..6b6c806d64 100644 --- a/src/d/actor/d_a_obj_lv8OptiLift.cpp +++ b/src/d/actor/d_a_obj_lv8OptiLift.cpp @@ -11,6 +11,10 @@ #include "d/d_com_inf_game.h" #include "d/d_path.h" +#if TARGET_PC +#include "dusk/frame_interpolation.h" +#endif + daOptiLift_HIO_c::daOptiLift_HIO_c() { mStopDisappearTime = 30; mStartMoveTime = 30; @@ -412,7 +416,44 @@ void daOptiLift_c::setNextPoint() { mCurrentPoint = next_point; } +#if TARGET_PC +static void daOptiLift_interp_callback(bool isSimFrame, void* pUserWork) { + daOptiLift_c* lift = static_cast(pUserWork); + if (lift == NULL || lift->mpModel == NULL) { + return; + } + + g_env_light.settingTevStruct(0x10, &lift->current.pos, &lift->tevStr); + g_env_light.setLightTevColorType_MAJI(lift->mpModel, &lift->tevStr); + + J3DModelData* modelData = lift->mpModel->getModelData(); + J3DMaterial* materialp = modelData->getMaterialNodePointer(0); + + if (materialp->getTexGenBlock()->getTexMtx(1) != NULL) { + J3DTexMtxInfo* mtx_info = &materialp->getTexGenBlock()->getTexMtx(1)->getTexMtxInfo(); + if (mtx_info != NULL) { + Mtx m; + C_MTXLightOrtho(m, 100.0f, -100.0f, -100.0f, 100.0f, 1.0f, 1.0f, 0.0f, 0.0f); + mDoMtx_stack_c::XrotS(0x4000); + mDoMtx_stack_c::transM(-lift->current.pos.x, -lift->current.pos.y, -lift->current.pos.z); + cMtx_concat(m, mDoMtx_stack_c::get(), mtx_info->mEffectMtx); + } + } + + lift->mBtk.entry(modelData); + + J3DGXColor* color = materialp->getTevKColor(1); + color->r = l_HIO.mColorR; + color->g = l_HIO.mColorG; + color->b = l_HIO.mColorB; +} +#endif + int daOptiLift_c::Draw() { +#if TARGET_PC + dusk::frame_interp::add_interpolation_callback(&daOptiLift_interp_callback, this); +#endif + g_env_light.settingTevStruct(0x10, ¤t.pos, &tevStr); g_env_light.setLightTevColorType_MAJI(mpModel, &tevStr); 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..6148d23e3a 100644 --- a/src/d/actor/d_a_player.cpp +++ b/src/d/actor/d_a_player.cpp @@ -369,18 +369,17 @@ JKRHeap* daPy_anmHeap_c::setAnimeHeap() { #if !PLATFORM_WII #if TARGET_PC #include "dusk/dvd_asset.hpp" +using GameVersion = dusk::version::GameVersion; static const u8* l_sightDL_get() { static u8 buf[0x89]; static bool _ = ( dusk::LoadDolAsset( buf, - #if VERSION == VERSION_GCN_PAL - 0x803BBDA0, - #elif VERSION == VERSION_GCN_JPN - 0x803B4220, - #elif VERSION == VERSION_GCN_USA - 0x803BA0C0, - #endif +{ + {GameVersion::GcnUsa, 0x803BA0C0}, + {GameVersion::GcnPal, 0x803BBDA0}, + {GameVersion::GcnJpn, 0x803B4220} + }, 0x89 ), true @@ -421,13 +420,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_a_title.cpp b/src/d/actor/d_a_title.cpp index 6df52abc5b..a75a96cb2e 100644 --- a/src/d/actor/d_a_title.cpp +++ b/src/d/actor/d_a_title.cpp @@ -1,21 +1,22 @@ #include "d/dolzel_rel.h" // IWYU pragma: keep +#include "JSystem/J2DGraph/J2DScreen.h" +#include "JSystem/J2DGraph/J2DTextBox.h" +#include "JSystem/JKernel/JKRExpHeap.h" +#include "JSystem/JKernel/JKRMemArchive.h" #include "d/actor/d_a_title.h" +#include "d/d_com_inf_game.h" +#include "d/d_demo.h" +#include "d/d_menu_collect.h" +#include "d/d_pane_class_alpha.h" #include "d/d_s_logo.h" #include "d/d_s_play.h" -#include "d/d_demo.h" -#include "d/d_pane_class_alpha.h" -#include "d/d_menu_collect.h" +#include "dusk/version.hpp" +#include "f_op/f_op_msg_mng.h" +#include "f_op/f_op_overlap_mng.h" +#include "f_op/f_op_scene_mng.h" #include "m_Do/m_Do_Reset.h" #include "m_Do/m_Do_controller_pad.h" -#include "d/d_com_inf_game.h" -#include "JSystem/JKernel/JKRExpHeap.h" -#include "f_op/f_op_overlap_mng.h" -#include "f_op/f_op_msg_mng.h" -#include "f_op/f_op_scene_mng.h" -#include "JSystem/J2DGraph/J2DScreen.h" -#include "JSystem/JKernel/JKRMemArchive.h" -#include "JSystem/J2DGraph/J2DTextBox.h" #include "m_Do/m_Do_graphic.h" #ifdef TARGET_PC @@ -49,7 +50,10 @@ static u8 const lit_3772[12] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }; -#if VERSION == VERSION_GCN_PAL +#if TARGET_PC +using namespace dusk::version; +#define l_arcName versionSelect({{GameVersion::GcnPal, "TitlePal"}}, "Title") +#elif VERSION == VERSION_GCN_PAL static char const l_arcName[] = "TitlePal"; #else static char const l_arcName[] = "Title"; @@ -59,7 +63,7 @@ daTit_HIO_c::daTit_HIO_c() { mPSScaleX = 1.0f; mPSScaleY = 1.0f; - #if VERSION == VERSION_GCN_PAL + #if TARGET_PC || VERSION == VERSION_GCN_PAL switch (OSGetLanguage()) { case OS_LANGUAGE_ENGLISH: case OS_LANGUAGE_GERMAN: diff --git a/src/d/actor/d_flower.inc b/src/d/actor/d_flower.inc index 034d35961e..4024d5ad6a 100644 --- a/src/d/actor/d_flower.inc +++ b/src/d/actor/d_flower.inc @@ -7,17 +7,13 @@ #include "dusk/frame_interpolation.h" -#if TARGET_PC -const u16 l_J_Ohana00_64TEX__width = 64; -const u16 l_J_Ohana00_64TEX__height = 64; -#else const u16 l_J_Ohana00_64TEX__width = 63; const u16 l_J_Ohana00_64TEX__height = 63; -#endif #if TARGET_PC #include "dusk/dvd_asset.hpp" -static u8* l_J_Ohana00_64TEX_get() { static u8 buf[0x800]; static bool _ = (dusk::LoadArchivedRelAsset(buf, 'AMEM', "d_a_grass.rel", 0x9060, 0x800), true); return buf; } +using GameVersion = dusk::version::GameVersion; +static u8* l_J_Ohana00_64TEX_get() { static u8 buf[0x800]; static bool _ = (dusk::LoadArchivedRelAsset(buf, 'AMEM', "d_a_grass.rel", {{GameVersion::GcnUsa, 0x9060}, {GameVersion::GcnPal, 0x9060}}, 0x800), true); return buf; } #define l_J_Ohana00_64TEX (l_J_Ohana00_64TEX_get()) #else #include "assets/l_J_Ohana00_64TEX.h" @@ -113,10 +109,12 @@ static u8 l_flowerTexCoord[] = { 0x3E, 0xA7, 0x72, 0xD6, 0xBD, 0x2F, 0x46, 0xAA}; #if TARGET_PC -static u8* l_J_hana00DL_get() { static u8 buf[0x150]; static bool _ = (dusk::LoadArchivedRelAsset(buf, 'AMEM', "d_a_grass.rel", 0x9D20, 0x150), true); return buf; } -static u8* l_J_hana00_cDL_get() { static u8 buf[0xDE]; static bool _ = (dusk::LoadArchivedRelAsset(buf, 'AMEM', "d_a_grass.rel", 0x9E80, 0xDE), true); return buf; } -static u8* l_matDL_get() { static u8 buf[0x99]; static bool _ = (dusk::LoadArchivedRelAsset(buf, 'AMEM', "d_a_grass.rel", 0x9F60, 0x99), true); return buf; } -static u8* l_matLight4DL_get() { static u8 buf[0x99]; static bool _ = (dusk::LoadArchivedRelAsset(buf, 'AMEM', "d_a_grass.rel", 0xA000, 0x99), true); return buf; } +using GameVersion = dusk::version::GameVersion; + +static u8* l_J_hana00DL_get() { static u8 buf[0x150]; static bool _ = (dusk::LoadArchivedRelAsset(buf, 'AMEM', "d_a_grass.rel", {{GameVersion::GcnUsa, 0x9D20}, {GameVersion::GcnPal, 0x9D20}}, 0x150), true); return buf; } +static u8* l_J_hana00_cDL_get() { static u8 buf[0xDE]; static bool _ = (dusk::LoadArchivedRelAsset(buf, 'AMEM', "d_a_grass.rel", {{GameVersion::GcnUsa, 0x9E80}, {GameVersion::GcnPal, 0x9E80}}, 0xDE), true); return buf; } +static u8* l_matDL_get() { static u8 buf[0x99]; static bool _ = (dusk::LoadArchivedRelAsset(buf, 'AMEM', "d_a_grass.rel", {{GameVersion::GcnUsa, 0x9F60}, {GameVersion::GcnPal, 0x9F60}}, 0x99), true); return buf; } +static u8* l_matLight4DL_get() { static u8 buf[0x99]; static bool _ = (dusk::LoadArchivedRelAsset(buf, 'AMEM', "d_a_grass.rel", {{GameVersion::GcnUsa, 0xA000}, {GameVersion::GcnPal, 0xA000}}, 0x99), true); return buf; } #define l_J_hana00DL (l_J_hana00DL_get()) #define l_J_hana00_cDL (l_J_hana00_cDL_get()) #define l_matDL (l_matDL_get()) @@ -133,16 +131,12 @@ l_matDL__d_a_grass(l_J_Ohana00_64TEX) l_matLight4DL(l_J_Ohana00_64TEX) #endif -#if TARGET_PC -const u16 l_J_Ohana01_64128_0419TEX__width = 64; -const u16 l_J_Ohana01_64128_0419TEX__height = 128; -#else const u16 l_J_Ohana01_64128_0419TEX__width = 63; const u16 l_J_Ohana01_64128_0419TEX__height = 127; -#endif #if TARGET_PC -static u8* l_J_Ohana01_64128_0419TEX_get() { static u8 buf[0x1000]; static bool _ = (dusk::LoadArchivedRelAsset(buf, 'AMEM', "d_a_grass.rel", 0xA0A0, 0x1000), true); return buf; } +using GameVersion = dusk::version::GameVersion; +static u8* l_J_Ohana01_64128_0419TEX_get() { static u8 buf[0x1000]; static bool _ = (dusk::LoadArchivedRelAsset(buf, 'AMEM', "d_a_grass.rel", {{GameVersion::GcnUsa, 0xA0A0}, {GameVersion::GcnPal, 0xA0A0}}, 0x1000), true); return buf; } #define l_J_Ohana01_64128_0419TEX (l_J_Ohana01_64128_0419TEX_get()) #else #include "assets/l_J_Ohana01_64128_0419TEX.h" @@ -274,11 +268,13 @@ static u8 l_flowerTexCoord2[] = { 0x40, 0x1B, 0x7D, 0x52, 0x3F, 0x80, 0x3F, 0x79, 0x40, 0x1B, 0x7D, 0x52, 0x3F, 0x51, 0x10, 0x6F}; #if TARGET_PC -static u8* l_J_hana01DL_get() { static u8 buf[0x138]; static bool _ = (dusk::LoadArchivedRelAsset(buf, 'AMEM', "d_a_grass.rel", 0xB7C0, 0x138), true); return buf; } -static u8* l_J_hana01_c_00DL_get() { static u8 buf[0xDE]; static bool _ = (dusk::LoadArchivedRelAsset(buf, 'AMEM', "d_a_grass.rel", 0xB900, 0xDE), true); return buf; } -static u8* l_J_hana01_c_01DL_get() { static u8 buf[0x128]; static bool _ = (dusk::LoadArchivedRelAsset(buf, 'AMEM', "d_a_grass.rel", 0xB9E0, 0x128), true); return buf; } -static u8* l_mat2DL_get() { static u8 buf[0x99]; static bool _ = (dusk::LoadArchivedRelAsset(buf, 'AMEM', "d_a_grass.rel", 0xBB20, 0x99), true); return buf; } -static u8* l_mat2Light4DL_get() { static u8 buf[0x99]; static bool _ = (dusk::LoadArchivedRelAsset(buf, 'AMEM', "d_a_grass.rel", 0xBBC0, 0x99), true); return buf; } +using GameVersion = dusk::version::GameVersion; + +static u8* l_J_hana01DL_get() { static u8 buf[0x138]; static bool _ = (dusk::LoadArchivedRelAsset(buf, 'AMEM', "d_a_grass.rel", {{GameVersion::GcnUsa, 0xB7C0}, {GameVersion::GcnPal, 0xB7C0}}, 0x138), true); return buf; } +static u8* l_J_hana01_c_00DL_get() { static u8 buf[0xDE]; static bool _ = (dusk::LoadArchivedRelAsset(buf, 'AMEM', "d_a_grass.rel", {{GameVersion::GcnUsa, 0xB900}, {GameVersion::GcnPal, 0xB900}}, 0xDE), true); return buf; } +static u8* l_J_hana01_c_01DL_get() { static u8 buf[0x128]; static bool _ = (dusk::LoadArchivedRelAsset(buf, 'AMEM', "d_a_grass.rel", {{GameVersion::GcnUsa, 0xB9E0}, {GameVersion::GcnPal, 0xB9E0}}, 0x128), true); return buf; } +static u8* l_mat2DL_get() { static u8 buf[0x99]; static bool _ = (dusk::LoadArchivedRelAsset(buf, 'AMEM', "d_a_grass.rel", {{GameVersion::GcnUsa, 0xBB20}, {GameVersion::GcnPal, 0xBB20}}, 0x99), true); return buf; } +static u8* l_mat2Light4DL_get() { static u8 buf[0x99]; static bool _ = (dusk::LoadArchivedRelAsset(buf, 'AMEM', "d_a_grass.rel", {{GameVersion::GcnUsa, 0xBBC0}, {GameVersion::GcnPal, 0xBBC0}}, 0x99), true); return buf; } #define l_J_hana01DL (l_J_hana01DL_get()) #define l_J_hana01_c_00DL (l_J_hana01_c_00DL_get()) #define l_J_hana01_c_01DL (l_J_hana01_c_01DL_get()) @@ -586,11 +582,11 @@ dFlower_packet_c::dFlower_packet_c() { #if TARGET_PC GXInitTexObj(&mTexObj_l_J_Ohana00_64TEX, l_J_Ohana00_64TEX, - l_J_Ohana00_64TEX__width, l_J_Ohana00_64TEX__height, GX_TF_CMPR, GX_MIRROR, GX_MIRROR, GX_FALSE + l_J_Ohana00_64TEX__width + 1, l_J_Ohana00_64TEX__height + 1, GX_TF_CMPR, GX_MIRROR, GX_MIRROR, GX_FALSE ); GXInitTexObj(&mTexObj_l_J_Ohana01_64128_0419TEX, l_J_Ohana01_64128_0419TEX, - l_J_Ohana01_64128_0419TEX__width, l_J_Ohana01_64128_0419TEX__height, GX_TF_CMPR, GX_MIRROR, GX_MIRROR, GX_FALSE + l_J_Ohana01_64128_0419TEX__width + 1, l_J_Ohana01_64128_0419TEX__height + 1, GX_TF_CMPR, GX_MIRROR, GX_MIRROR, GX_FALSE ); #endif @@ -699,14 +695,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 @@ -789,6 +785,10 @@ void dFlower_packet_c::draw() { GXColor sp28; + #if AVOID_UB + sp28 = {1, 1, 1, 1}; + #endif + //u8 sp26, sp25, sp24; GXColor sp24; sp24.r = -0.4f * temp_r29->AmbCol.r * var_f29; @@ -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..3d67a57007 100644 --- a/src/d/actor/d_grass.inc +++ b/src/d/actor/d_grass.inc @@ -20,8 +20,9 @@ const u16 l_M_kusa05_RGBATEX__height = 31; #if TARGET_PC #include "dusk/dvd_asset.hpp" -static u8* l_M_kusa05_RGBATEX_get() { static u8 buf[0x800]; static bool _ = (dusk::LoadArchivedRelAsset(buf, 'AMEM', "d_a_grass.rel", 0x7680, 0x800), true); return buf; } -static u8* l_M_Hijiki00TEX_get() { static u8 buf[0x800]; static bool _ = (dusk::LoadArchivedRelAsset(buf, 'AMEM', "d_a_grass.rel", 0x7E80, 0x800), true); return buf; } +using GameVersion = dusk::version::GameVersion; +static u8* l_M_kusa05_RGBATEX_get() { static u8 buf[0x800]; static bool _ = (dusk::LoadArchivedRelAsset(buf, 'AMEM', "d_a_grass.rel", {{GameVersion::GcnUsa, 0x7680}, {GameVersion::GcnPal, 0x7680}}, 0x800), true); return buf; } +static u8* l_M_Hijiki00TEX_get() { static u8 buf[0x800]; static bool _ = (dusk::LoadArchivedRelAsset(buf, 'AMEM', "d_a_grass.rel", {{GameVersion::GcnUsa, 0x7E80}, {GameVersion::GcnPal, 0x7E80}}, 0x800), true); return buf; } #define l_M_kusa05_RGBATEX (l_M_kusa05_RGBATEX_get()) #define l_M_Hijiki00TEX (l_M_Hijiki00TEX_get()) #else @@ -113,12 +114,14 @@ static u8 l_texCoord[160] = { }; #if TARGET_PC -static u8* l_M_Kusa_9qDL_get() { static u8 buf[0xCB]; static bool _ = (dusk::LoadArchivedRelAsset(buf, 'AMEM', "d_a_grass.rel", 0x8B00, 0xCB), true); return buf; } -static u8* l_M_Kusa_9q_cDL_get() { static u8 buf[0xCB]; static bool _ = (dusk::LoadArchivedRelAsset(buf, 'AMEM', "d_a_grass.rel", 0x8BE0, 0xCB), true); return buf; } -static u8* l_M_TenGusaDL_get() { static u8 buf[0xD4]; static bool _ = (dusk::LoadArchivedRelAsset(buf, 'AMEM', "d_a_grass.rel", 0x8CC0, 0xD4), true); return buf; } -static u8* l_Tengusa_matDL_get() { static u8 buf[0xA8]; static bool _ = (dusk::LoadArchivedRelAsset(buf, 'AMEM', "d_a_grass.rel", 0x8DA0, 0xA8), true); return buf; } -static u8* l_kusa9q_matDL_get() { static u8 buf[0xA8]; static bool _ = (dusk::LoadArchivedRelAsset(buf, 'AMEM', "d_a_grass.rel", 0x8E60, 0xA8), true); return buf; } -static u8* l_kusa9q_l4_matDL_get() { static u8 buf[0xA8]; static bool _ = (dusk::LoadArchivedRelAsset(buf, 'AMEM', "d_a_grass.rel", 0x8F20, 0xA8), true); return buf; } +using GameVersion = dusk::version::GameVersion; + +static u8* l_M_Kusa_9qDL_get() { static u8 buf[0xCB]; static bool _ = (dusk::LoadArchivedRelAsset(buf, 'AMEM', "d_a_grass.rel", {{GameVersion::GcnUsa, 0x8B00}, {GameVersion::GcnPal, 0x8B00}}, 0xCB), true); return buf; } +static u8* l_M_Kusa_9q_cDL_get() { static u8 buf[0xCB]; static bool _ = (dusk::LoadArchivedRelAsset(buf, 'AMEM', "d_a_grass.rel", {{GameVersion::GcnUsa, 0x8BE0}, {GameVersion::GcnPal, 0x8BE0}}, 0xCB), true); return buf; } +static u8* l_M_TenGusaDL_get() { static u8 buf[0xD4]; static bool _ = (dusk::LoadArchivedRelAsset(buf, 'AMEM', "d_a_grass.rel", {{GameVersion::GcnUsa, 0x8CC0}, {GameVersion::GcnPal, 0x8CC0}}, 0xD4), true); return buf; } +static u8* l_Tengusa_matDL_get() { static u8 buf[0xA8]; static bool _ = (dusk::LoadArchivedRelAsset(buf, 'AMEM', "d_a_grass.rel", {{GameVersion::GcnUsa, 0x8DA0}, {GameVersion::GcnPal, 0x8DA0}}, 0xA8), true); return buf; } +static u8* l_kusa9q_matDL_get() { static u8 buf[0xA8]; static bool _ = (dusk::LoadArchivedRelAsset(buf, 'AMEM', "d_a_grass.rel", {{GameVersion::GcnUsa, 0x8E60}, {GameVersion::GcnPal, 0x8E60}}, 0xA8), true); return buf; } +static u8* l_kusa9q_l4_matDL_get() { static u8 buf[0xA8]; static bool _ = (dusk::LoadArchivedRelAsset(buf, 'AMEM', "d_a_grass.rel", {{GameVersion::GcnUsa, 0x8F20}, {GameVersion::GcnPal, 0x8F20}}, 0xA8), true); return buf; } #define l_M_Kusa_9qDL (l_M_Kusa_9qDL_get()) #define l_M_Kusa_9q_cDL (l_M_Kusa_9q_cDL_get()) #define l_M_TenGusaDL (l_M_TenGusaDL_get()) @@ -491,11 +494,11 @@ dGrass_packet_c::dGrass_packet_c() { #if TARGET_PC GXInitTexObj(&mTexObj_l_M_kusa05_RGBATEX, l_M_kusa05_RGBATEX, - l_M_kusa05_RGBATEX__width, l_M_kusa05_RGBATEX__height, GX_TF_RGB5A3, GX_REPEAT, GX_CLAMP, GX_FALSE + l_M_kusa05_RGBATEX__width + 1, l_M_kusa05_RGBATEX__height + 1, GX_TF_RGB5A3, GX_REPEAT, GX_CLAMP, GX_FALSE ); GXInitTexObj(&mTexObj_l_M_Hijiki00TEX, l_M_Hijiki00TEX, - l_M_Hijiki00TEX__width, l_M_Hijiki00TEX__height, GX_TF_RGB5A3, GX_REPEAT, GX_CLAMP, GX_FALSE + l_M_Hijiki00TEX__width + 1, l_M_Hijiki00TEX__height + 1, GX_TF_RGB5A3, GX_REPEAT, GX_CLAMP, GX_FALSE ); #endif @@ -643,18 +646,14 @@ void dGrass_packet_c::draw() { } if (var_r29->field_0x05 <= 3 || var_r29->field_0x05 >= 10) { -#if TARGET_PC - GXLoadTexObj(&mTexObj_l_M_kusa05_RGBATEX, GX_TEXMAP0); -#endif + IF_DUSK(GXLoadTexObj(&mTexObj_l_M_kusa05_RGBATEX, GX_TEXMAP0)); if (sp48 <= 3) { GXCallDisplayList(mp_kusa9q_14_DL, m_kusa9q_DL_14_size); } else { GXCallDisplayList(mp_kusa9q_DL, m_kusa9q_DL_size); } } else { -#if TARGET_PC - GXLoadTexObj(&mTexObj_l_M_Hijiki00TEX, GX_TEXMAP0); -#endif + IF_DUSK(GXLoadTexObj(&mTexObj_l_M_Hijiki00TEX, GX_TEXMAP0)); GXCallDisplayList(l_Tengusa_matDL, 0xA0); } @@ -680,12 +679,14 @@ void dGrass_packet_c::draw() { while (var_r29 != NULL) { if (var_r29->field_0x05 <= 3 || var_r29->field_0x05 >= 10) { + IF_DUSK(GXLoadTexObj(&mTexObj_l_M_kusa05_RGBATEX, GX_TEXMAP0)); if (sp48 <= 2) { GXCallDisplayList(mp_kusa9q_14_DL, m_kusa9q_DL_14_size); } else { GXCallDisplayList(mp_kusa9q_DL, m_kusa9q_DL_size); } } else { + IF_DUSK(GXLoadTexObj(&mTexObj_l_M_Hijiki00TEX, GX_TEXMAP0)); GXCallDisplayList(l_Tengusa_matDL, 0xA0); } @@ -756,13 +757,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 +1019,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..3ee98be5c7 100644 --- a/src/d/d_bright_check.cpp +++ b/src/d/d_bright_check.cpp @@ -9,6 +9,8 @@ #include "JSystem/J2DGraph/J2DScreen.h" #include "JSystem/J2DGraph/J2DTextBox.h" #include "d/d_msg_string.h" +#include "dusk/livesplit.h" +#include "dusk/imgui/ImGuiConsole.hpp" #include "m_Do/m_Do_controller_pad.h" dBrightCheck_c::dBrightCheck_c(JKRArchive* i_archive) { @@ -138,6 +140,17 @@ void dBrightCheck_c::modeWait() {} void dBrightCheck_c::modeMove() { if (mDoCPd_c::getTrigA(PAD_1) || mDoCPd_c::getTrigStart(PAD_1)) { mDoAud_seStart(Z2SE_ENTER_GAME, NULL, 0, 0); +#ifdef TARGET_PC + dusk::speedrun::start(); + + if (dusk::getSettings().game.speedrunMode && !dusk::getSettings().game.hideTvSettingsScreen) { + // start a new run if a run isn't already in progress + if (!dusk::m_speedrunInfo.m_isRunStarted) { + dusk::ImGuiMenuGame::resetForSpeedrunMode(); + dusk::m_speedrunInfo.startRun(); + } + } +#endif mCompleteCheck = true; mMode = MODE_WAIT_e; } @@ -147,7 +160,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 b93d569003..c59cf1c525 100644 --- a/src/d/d_camera.cpp +++ b/src/d/d_camera.cpp @@ -800,6 +800,10 @@ void dCamera_c::updatePad() { } mLockLActive = 1; + + #if TARGET_PC + mCamParam.mManualMode = 0; + #endif } else { mLockLJustActivated = 0; mLockLActive = 0; @@ -833,6 +837,12 @@ void dCamera_c::updatePad() { mHoldB = mDoCPd_c::getHoldB(mPadID) ? true : false; mTrigB = mDoCPd_c::getTrigB(mPadID) ? true : false; + #if TARGET_PC + if (mCamParam.mManualMode) { + return; + } + #endif + bool sp6B = true; bool sp6C = true; int temp1; @@ -1167,6 +1177,13 @@ bool dCamera_c::Run() { } } else { sp0F = (this->*engine_tbl[mCamParam.Algorythmn(mCamStyle)])(mCamStyle); + + #if TARGET_PC + if (mCamParam.Algorythmn(mCamStyle) != 1) { + mCamParam.mManualMode = 0; + } + #endif + field_0x170++; field_0x160++; mCurCamStyleTimer++; @@ -3078,6 +3095,11 @@ bool dCamera_c::bumpCheck(u32 i_flags) { } else { field_0x968 *= mMonitor.field_0xc / 5.0f; } + + #if TARGET_PC + if (!dusk::getSettings().game.freeCamera || !mCamParam.mManualMode) { + #endif + f32 tmp = field_0x96c * (mIsWolf == 1 ? 30.0f : 30.0f); center += vec3.norm() * (tmp * globe.V().Sin()); cSGlobe globe2(vec2 - center); @@ -3091,6 +3113,10 @@ bool dCamera_c::bumpCheck(u32 i_flags) { vec = lin_chk1.GetCross(); } + #if TARGET_PC + } + #endif + #if DEBUG if (mCamSetup.CheckFlag(0x8000)) { dDbVw_Report(20, 235, " U"); @@ -4604,6 +4630,11 @@ bool dCamera_c::chaseCamera(s32 param_0) { sp110 = mViewCache.mDirection.R(); mViewCache.mDirection.R(mViewCache.mDirection.R() + (fVar55 - mViewCache.mDirection.R()) * chase->field_0x74); + + #if TARGET_PC + freeCamera(); + #endif + chase->field_0x64 = mViewCache.mCenter + mViewCache.mDirection.Xyz(); mViewCache.mEye = chase->field_0x64; @@ -7063,6 +7094,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; @@ -7435,6 +7475,40 @@ bool dCamera_c::test2Camera(s32 param_0) { return false; } +#if TARGET_PC +bool dCamera_c::freeCamera() { + if (!dusk::getSettings().game.freeCamera) { + mCamParam.mManualMode = 0; + return false; + } + + cXyz camMovement = {mPadInfo.mCStick.mLastPosX, mPadInfo.mCStick.mLastPosY, 0.0f}; + f32 magnitude = sqrt(mPadInfo.mCStick.mLastPosX * mPadInfo.mCStick.mLastPosX + mPadInfo.mCStick.mLastPosY * mPadInfo.mCStick.mLastPosY); + + if (mPadInfo.mCStick.mLastPosX != 0 || mPadInfo.mCStick.mLastPosY != 0) { + if (!mCamParam.mManualMode) { + mCamParam.mManualMode = 1; + mCamParam.freeXAngle = mViewCache.mDirection.mAzimuth.Degree(); + mCamParam.freeYAngle = mViewCache.mDirection.mInclination.Degree(); + } + + camMovement = camMovement.normalize(); + camMovement.y *= dusk::getSettings().game.invertCameraYAxis ? 1.0f : -1.0f; + mCamParam.freeXAngle += camMovement.x * magnitude * dusk::getSettings().game.freeCameraSensitivity * 4.0f; + mCamParam.freeYAngle += camMovement.y * magnitude * dusk::getSettings().game.freeCameraSensitivity * 4.0f; + } + + if (mCamParam.mManualMode) { + mCamParam.freeYAngle = std::clamp(mCamParam.freeYAngle, -35.0f, 60.0f); + mViewCache.mDirection.mAzimuth = cSAngle(mCamParam.freeXAngle); + mViewCache.mDirection.mInclination = cSAngle(mCamParam.freeYAngle); + mViewCache.mDirection.mRadius = std::clamp((mCamParam.freeYAngle + 35.0f) * 10.0f, 300.0f, 10000.0f); + } + + return mCamParam.mManualMode; +} +#endif + bool dCamera_c::towerCamera(s32 param_0) { cSAngle stack_444 = cSAngle(mCamSetup.ChargeLatitude()); f32 sp224 = mCamSetup.ChargeBRatio(); @@ -11009,6 +11083,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,9 +11160,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); -#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_sel_info.cpp b/src/d/d_file_sel_info.cpp index f305f59a9e..0c987e6d63 100644 --- a/src/d/d_file_sel_info.cpp +++ b/src/d/d_file_sel_info.cpp @@ -14,6 +14,8 @@ #include #include +#include "dusk/version.hpp" + dFile_info_c::dFile_info_c(JKRArchive* i_archive, u8 param_1) { mArchive = i_archive; field_0x22 = param_1; @@ -169,7 +171,18 @@ void dFile_info_c::setSaveDate(dSv_save_c* i_savedata) { OSCalendarTime time; OSTicksToCalendarTime(i_savedata->getPlayer().getPlayerStatusB().getDateIpl(), &time); - #if (VERSION == VERSION_GCN_JPN) || (VERSION == VERSION_WII_JPN) + #if TARGET_PC + if (dusk::version::isRegionJpn()) { + sprintf(mSaveDate, "%d.%02d.%02d %02d:%02d", time.year, time.mon + 1, time.mday, + time.hour, time.min); + } else if (dusk::version::isRegionPal() && dComIfGs_getPalLanguage() != dSv_player_config_c::LANGUAGE_ENGLISH) { + sprintf(mSaveDate, "%02d/%02d/%d %02d:%02d", time.mday, time.mon + 1, time.year, time.hour, + time.min); + } else { + sprintf(mSaveDate, "%02d/%02d/%d %02d:%02d", time.mon + 1, time.mday, time.year, time.hour, + time.min); + } + #elif (VERSION == VERSION_GCN_JPN) || (VERSION == VERSION_WII_JPN) sprintf(mSaveDate, "%d.%02d.%02d %02d:%02d", time.year, time.mon + 1, time.mday, time.hour, time.min); #elif VERSION == VERSION_GCN_PAL 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 9bb96efc82..6b84fbae43 100644 --- a/src/d/d_kankyo.cpp +++ b/src/d/d_kankyo.cpp @@ -35,6 +35,7 @@ #if TARGET_PC #include "dusk/imgui/ImGuiBloomWindow.hpp" #include "dusk/settings.h" +#include "dusk/frame_interpolation.h" #endif static void GxXFog_set(); @@ -8251,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(); @@ -11004,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 31ba59e33e..09e230c9fd 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; @@ -4127,11 +4117,7 @@ void dKyr_drawStar(Mtx drawMtx, u8** tex) { } } -#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. @@ -4280,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; @@ -4699,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; } @@ -4968,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; @@ -5023,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; @@ -5994,6 +5962,8 @@ static void dKyr_evil_draw2(Mtx drawMtx, u8** tex) { fopAc_ac_c* player = dComIfGp_getPlayer(0); if (evil_packet != NULL) { + IF_DUSK(GXPushDebugGroup("dKyr_evil_draw2")); + j3dSys.reinitGX(); if (dComIfGd_getView() != NULL) { MTXInverse(dComIfGd_getView()->viewMtxNoTrans, camMtx); @@ -6018,7 +5988,12 @@ static void dKyr_evil_draw2(Mtx drawMtx, u8** tex) { TGXTexObj texobj; dKyr_set_btitex(&texobj, (ResTIMG*)tex[1]); - rot += 0.7f; +#if TARGET_PC + if (dusk::frame_interp::get_ui_tick_pending()) +#endif + { + rot += 0.7f; + } MTXRotRad(rotMtx, 'Z', DEG_TO_RAD(rot)); MTXConcat(camMtx, rotMtx, camMtx); @@ -6068,14 +6043,15 @@ 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)) || +#if TARGET_PC + f32 cullMinX = mDoGph_gInf_c::getSafeMinXF() - sp34.x; + f32 cullMaxX = mDoGph_gInf_c::getSafeMinXF() + mDoGph_gInf_c::getSafeWidthF() + sp34.x; +#else + f32 cullMinX = -sp34.x; + f32 cullMaxX = FB_WIDTH + sp34.x; +#endif + if (!(proj.x > cullMinX) || !(proj.x < cullMaxX) || !(proj.y > -sp34.y) || !(proj.y < (458.0f + sp34.z))) { continue; @@ -6188,6 +6164,8 @@ static void dKyr_evil_draw2(Mtx drawMtx, u8** tex) { } } + IF_DUSK(GXPopDebugGroup()); + GXSetClipMode(GX_CLIP_ENABLE); J3DShape::resetVcdVatCache(); } @@ -6225,6 +6203,8 @@ void dKyr_evil_draw(Mtx drawMtx, u8** tex) { f32 sp60 = fabsf(cM_ssin(g_Counter.mCounter0 * 215)); if (evil_packet != NULL) { + IF_DUSK(GXPushDebugGroup("dKyr_evil_draw")); + j3dSys.reinitGX(); if (dComIfGd_getView() != NULL) { MTXInverse(dComIfGd_getView()->viewMtxNoTrans, camMtx); @@ -6246,14 +6226,19 @@ void dKyr_evil_draw(Mtx drawMtx, u8** tex) { TGXTexObj texobj; dKyr_set_btitex(&texobj, (ResTIMG*)tex[0]); - rot += 1.0f; +#if TARGET_PC + if (dusk::frame_interp::get_ui_tick_pending()) +#endif + { + rot += 1.0f; + } MTXRotRad(rotMtx, 'Z', DEG_TO_RAD(rot)); MTXConcat(camMtx, rotMtx, camMtx); GXLoadPosMtxImm(drawMtx, GX_PNMTX0); GXSetCurrentMtx(GX_PNMTX0); - GXSetVtxAttrFmt(GX_VTXFMT0, GX_VA_POS, GX_CLR_RGBA, GX_F32, 0); - GXSetVtxAttrFmt(GX_VTXFMT0, GX_VA_TEX0, GX_CLR_RGBA, GX_RGBA4, 8); + GXSetVtxAttrFmt(GX_VTXFMT0, GX_VA_POS, GX_POS_XYZ, GX_F32, 0); + GXSetVtxAttrFmt(GX_VTXFMT0, GX_VA_TEX0, GX_TEX_ST, GX_RGBA4, 8); GXClearVtxDesc(); GXSetVtxDesc(GX_VA_POS, GX_DIRECT); GXSetVtxDesc(GX_VA_TEX0, GX_DIRECT); @@ -6276,6 +6261,19 @@ void dKyr_evil_draw(Mtx drawMtx, u8** tex) { GXSetClipMode(GX_CLIP_DISABLE); GXSetNumIndStages(0); +#if TARGET_PC + // move color_reg0 to vtx for perf + GXSetVtxAttrFmt(GX_VTXFMT0, GX_VA_CLR0, GX_CLR_RGBA, GX_RGBA8, 0); + GXSetVtxDesc(GX_VA_CLR0, GX_DIRECT); + GXSetNumChans(1); + GXSetChanCtrl(GX_COLOR0A0, GX_FALSE, GX_SRC_REG, GX_SRC_VTX, 0, GX_DF_NONE, GX_AF_NONE); + GXSetTevOrder(GX_TEVSTAGE0, GX_TEXCOORD0, GX_TEXMAP0, GX_COLOR0A0); + GXSetTevColorIn(GX_TEVSTAGE0, GX_CC_C1, GX_CC_RASC, GX_CC_TEXC, GX_CC_ZERO); + GXSetTevColorOp(GX_TEVSTAGE0, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_TRUE, GX_TEVPREV); + GXSetTevAlphaIn(GX_TEVSTAGE0, GX_CA_ZERO, GX_CA_TEXA, GX_CA_RASA, GX_CA_ZERO); + GXSetTevAlphaOp(GX_TEVSTAGE0, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_TRUE, GX_TEVPREV); +#endif + dComIfG_Ccsp()->PrepareMass(); for (int i = 0; i < g_env_light.field_0x1054; i++) { @@ -6299,13 +6297,15 @@ 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)) || +#if TARGET_PC + f32 cullMinX = mDoGph_gInf_c::getSafeMinXF() - sp44.x; + f32 cullMaxX = mDoGph_gInf_c::getSafeMinXF() + mDoGph_gInf_c::getSafeWidthF() + sp44.x; +#else + f32 cullMinX = -sp44.x; + f32 cullMaxX = FB_WIDTH + sp44.x; +#endif + if (!(proj.x > cullMinX) || !(proj.x < cullMaxX) || !(proj.y > -sp44.y) || !(proj.y < (458.0f + sp44.z))) { continue; @@ -6392,7 +6392,7 @@ void dKyr_evil_draw(Mtx drawMtx, u8** tex) { color_reg0.b = (115.0f * sp28) + (15.0f * fabsf(sp2C - sp64)); } - GXSetTevColor(GX_TEVREG0, color_reg0); + IF_NOT_DUSK(GXSetTevColor(GX_TEVREG0, color_reg0)); GXSetTevColor(GX_TEVREG1, color_reg1); spC8 = spA4; @@ -6431,12 +6431,16 @@ void dKyr_evil_draw(Mtx drawMtx, u8** tex) { GXBegin(GX_QUADS, GX_VTXFMT0, 4); GXPosition3f32(pos[0].x, pos[0].y, pos[0].z); + IF_DUSK(GXColor4u8(color_reg0.r, color_reg0.g, color_reg0.b, color_reg0.a)); GXTexCoord2s16(0, 0); GXPosition3f32(pos[1].x, pos[1].y, pos[1].z); + IF_DUSK(GXColor4u8(color_reg0.r, color_reg0.g, color_reg0.b, color_reg0.a)); GXTexCoord2s16(0xFF, 0); GXPosition3f32(pos[2].x, pos[2].y, pos[2].z); + IF_DUSK(GXColor4u8(color_reg0.r, color_reg0.g, color_reg0.b, color_reg0.a)); GXTexCoord2s16(0xFF, 0xFF); GXPosition3f32(pos[3].x, pos[3].y, pos[3].z); + IF_DUSK(GXColor4u8(color_reg0.r, color_reg0.g, color_reg0.b, color_reg0.a)); GXTexCoord2s16(0, 0xFF); GXEnd(); } @@ -6444,6 +6448,8 @@ void dKyr_evil_draw(Mtx drawMtx, u8** tex) { } } + IF_DUSK(GXPopDebugGroup()); + J3DShape::resetVcdVatCache(); GXSetClipMode(GX_CLIP_ENABLE); diff --git a/src/d/d_map.cpp b/src/d/d_map.cpp index 7dc5187a88..d4e5a98888 100644 --- a/src/d/d_map.cpp +++ b/src/d/d_map.cpp @@ -13,6 +13,9 @@ #include "SSystem/SComponent/c_math.h" #include "d/actor/d_a_player.h" #include "d/d_com_inf_game.h" +#if TARGET_PC +#include +#endif #include #if DEBUG @@ -539,17 +542,14 @@ void renderingAmap_c::rendering(dDrawPath_c::poly_class const* i_poly) { } } -/* Enabling the following definition will modify the following function to - * make the map look worse for extra speed in the emulator, especially in large - * areas such as hyrule field. - */ -#define HYRULE_FIELD_SPEEDHACK bool renderingAmap_c::isDrawOutSideTrim() { bool rt = false; - #ifdef HYRULE_FIELD_SPEEDHACK - return 0; + #if TARGET_PC + if (!dusk::getSettings().game.enableMapBackground) { + return 0; + } #endif if (getDispType() == 0 || getDispType() == 4 || getDispType() == 3 || getDispType() == 2 || @@ -1218,6 +1218,9 @@ void dMap_c::changeTextureSize(int param_1, int param_2, int param_3) { void dMap_c::_remove() { if (mImage_p != NULL) { +#if TARGET_PC + GXDestroyCopyTex(mImage_p); +#endif JKR_DELETE_ARRAY(mImage_p); mImage_p = NULL; } diff --git a/src/d/d_map_path.cpp b/src/d/d_map_path.cpp index a31ccdedfd..f94cdc9475 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() { @@ -241,12 +237,15 @@ void dDrawPath_c::rendering(dDrawPath_c::line_class const* p_line) { if (isDrawType(p_line->field_0x0)) { int width = getLineWidth(p_line->field_0x1); + #if TARGET_PC + f32 height = JUTVideo::getManager()->getRenderHeight() / 448.0f; + if (height > 1.0f) { + width /= 2; + } + #endif + 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 +434,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); @@ -494,12 +497,6 @@ void dRenderingFDAmap_c::postRenderingMap() { dMpath_n::dTexObjAggregate_c dMpath_n::m_texObjAgg; -/* Enabling the following definition will modify the following function to - * make the map look worse for extra speed in the emulator, especially in large - * areas such as hyrule field. - */ -#define HYRULE_FIELD_SPEEDHACK - void dRenderingFDAmap_c::renderingDecoration(dDrawPath_c::line_class const* p_line) { s32 width = getDecorationLineWidth(p_line->field_0x1); if (width <= 0) { @@ -517,20 +514,39 @@ 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; GXSetTevColor(GX_TEVREG1, lineColor); +#if TARGET_PC + GXSetTevColorIn(GX_TEVSTAGE0, GX_CC_ZERO, GX_CC_ZERO, GX_CC_ZERO, GX_CC_C0); + GXSetTevColorOp(GX_TEVSTAGE0, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_TRUE, GX_TEVPREV); + GXSetTevAlphaIn(GX_TEVSTAGE0, GX_CA_ZERO, GX_CA_ZERO, GX_CA_ZERO, GX_CA_KONST); + GXSetTevAlphaOp(GX_TEVSTAGE0, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_TRUE, GX_TEVPREV); + GXBegin(GX_LINESTRIP, GX_VTXFMT0, 2 * (data_num - 1)); + for (int i = 0; i < data_num - 1; i++) { + GXPosition1x16(data_p[i]); + GXTexCoord2f32(0, 0); + GXPosition1x16(data_p[i + 1]); + GXTexCoord2f32(0, 0); + } + GXEnd(); + + GXSetTevColorIn(GX_TEVSTAGE0, GX_CC_ZERO, GX_CC_KONST, GX_CC_TEXC, GX_CC_C1); + GXSetTevColorOp(GX_TEVSTAGE0, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_TRUE, GX_TEVPREV); + GXSetTevAlphaIn(GX_TEVSTAGE0, GX_CA_ZERO, GX_CA_ZERO, GX_CA_ZERO, GX_CA_TEXA); + GXSetTevAlphaOp(GX_TEVSTAGE0, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_TRUE, GX_TEVPREV); + GXBegin(GX_POINTS, GX_VTXFMT0, data_num); + for (int i = 0; i < data_num; i++) { + GXPosition1x16(data_p[i]); + GXTexCoord2f32(0, 0); + } + GXEnd(); +#else for (int i = 0; i < data_num; i++) { -#ifndef HYRULE_FIELD_SPEEDHACK if (i < data_num - 1) { GXSetTevColorIn(GX_TEVSTAGE0, GX_CC_ZERO, GX_CC_ZERO, GX_CC_ZERO, GX_CC_C0); GXSetTevColorOp(GX_TEVSTAGE0, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_TRUE, @@ -549,7 +565,6 @@ void dRenderingFDAmap_c::renderingDecoration(dDrawPath_c::line_class const* p_li GXSetTevColorOp(GX_TEVSTAGE0, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_TRUE, GX_TEVPREV); GXSetTevAlphaIn(GX_TEVSTAGE0, GX_CA_ZERO, GX_CA_ZERO, GX_CA_ZERO, GX_CA_TEXA); GXSetTevAlphaOp(GX_TEVSTAGE0, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_TRUE, GX_TEVPREV); -#endif GXBegin(GX_POINTS, GX_VTXFMT0, 1); GXPosition1x16(data_p[0]); @@ -557,6 +572,7 @@ void dRenderingFDAmap_c::renderingDecoration(dDrawPath_c::line_class const* p_li GXEnd(); data_p++; } +#endif setTevSettingNonTextureDirectColor(); GXClearVtxDesc(); diff --git a/src/d/d_menu_collect.cpp b/src/d/d_menu_collect.cpp index c7e5bb7414..612806d372 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 } } @@ -2400,6 +2399,13 @@ void dMenu_Collect3D_c::_move(u8 param_0, u8 param_1) { posZ = 550.0f; } toItem3Dpos(linkPos.x, posY, posZ, &itemPos); + +#if TARGET_PC + if (dusk::getSettings().game.enableLinkDollRotation) { + const f32 angle = mDoCPd_c::getSubStickX3D(PAD_1) * 2048; + ANGLE_ADD(mLinkAngle, angle); + } else +#endif if (param_0 == 0 && param_1 == 0) { f32 temp = 450.0f; ANGLE_ADD(mLinkAngle, temp); @@ -2688,12 +2694,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 +2707,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..bb557efdac 100644 --- a/src/d/d_menu_dmap.cpp +++ b/src/d/d_menu_dmap.cpp @@ -21,6 +21,7 @@ #include "d/d_msg_string.h" #include "d/d_meter_haihai.h" #include "d/d_menu_window.h" +#include "dusk/settings.h" #include "f_op/f_op_msg_mng.h" #include "m_Do/m_Do_graphic.h" #include @@ -864,33 +865,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 +905,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 +934,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(); @@ -973,9 +946,15 @@ void dMenu_DmapBg_c::draw() { mpMeterHaihai->drawHaihai(field_0xdda, x1 + (local_224.x + local_218.x) / 2, y1 + (local_224.y + local_218.y) / 2, - -35.0f + (local_224.x - local_218.x), + -35.0f + (local_224.x - local_218.x), -35.0f + (local_224.y - local_218.y)); +#if TARGET_PC + if (!dusk::getSettings().game.enableFrameInterpolation) { + field_0xdda = 0; + } +#else field_0xdda = 0; +#endif } dMenu_Dmap_c::myclass->drawFloorScreenTop(mFloorScreen, field_0xd94, field_0xd98, grafContext); @@ -1012,7 +991,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 } } @@ -2573,6 +2581,11 @@ void dMenu_Dmap_c::zoomIn_proc() { } void dMenu_Dmap_c::zoomOut_init_proc() { +#if TARGET_PC + if (dusk::getSettings().game.enableFrameInterpolation) { + mpDrawBg->resetScrollArrowMask(); + } +#endif Z2GetAudioMgr()->seStart(Z2SE_SY_MAP_ZOOMOUT, NULL, 0, 0, 1.0f, 1.0f, -1.0f, -1.0f, 0); mMapCtrl->initZoomOut(10); mpDrawBg->iconScaleAnmInit(1.0f, 0.0f, 10); diff --git a/src/d/d_menu_dmap_map.cpp b/src/d/d_menu_dmap_map.cpp index 8971b2630d..9100a7d63a 100644 --- a/src/d/d_menu_dmap_map.cpp +++ b/src/d/d_menu_dmap_map.cpp @@ -11,6 +11,9 @@ #include "d/d_menu_dmap_map.h" #include "f_op/f_op_msg_mng.h" #include "m_Do/m_Do_graphic.h" +#if TARGET_PC +#include +#endif struct dMdm_HIO_prm_res_dst_s { static void* m_res; @@ -291,6 +294,9 @@ void dMenu_DmapMap_c::_create(u16 param_0, u16 param_1, u16 param_2, u16 param_3 void dMenu_DmapMap_c::_delete() { for (int i = 0; i < 2; i++) { if (mMapImage_p[i] != NULL) { +#if TARGET_PC + GXDestroyCopyTex(mMapImage_p[i]); +#endif JKR_DELETE_ARRAY(mMapImage_p[i]); } diff --git a/src/d/d_menu_fishing.cpp b/src/d/d_menu_fishing.cpp index 615323767b..b76002d540 100644 --- a/src/d/d_menu_fishing.cpp +++ b/src/d/d_menu_fishing.cpp @@ -16,6 +16,8 @@ #include "m_Do/m_Do_graphic.h" #include +#include "dusk/version.hpp" + typedef void (dMenu_Fishing_c::*initFunc)(); initFunc map_init_process[] = { &dMenu_Fishing_c::wait_init, @@ -135,9 +137,9 @@ bool dMenu_Fishing_c::isSync() { } void dMenu_Fishing_c::init() { - #if VERSION == VERSION_GCN_PAL + #if TARGET_PC || VERSION == VERSION_GCN_PAL BOOL isEnglish = FALSE; - if (dComIfGs_getPalLanguage() == dSv_player_config_c::LANGUAGE_ENGLISH) { + if (dusk::version::isRegionUsa() || (dusk::version::isRegionPal() && dComIfGs_getPalLanguage() == dSv_player_config_c::LANGUAGE_ENGLISH)) { isEnglish = TRUE; } #endif @@ -145,7 +147,7 @@ void dMenu_Fishing_c::init() { for (int i = 0; i < MAX_FINDABLE_FISHES; i++) { if (dComIfGs_getFishNum(i) != 0) { // Fish has been caught once, display it along with it's params - #if VERSION == VERSION_GCN_PAL + #if TARGET_PC || VERSION == VERSION_GCN_PAL if (isEnglish) { setFishParam(i, dComIfGs_getFishNum(i), dComIfGs_getFishSize(i) / 2.54f); } else { diff --git a/src/d/d_menu_fmap2D.cpp b/src/d/d_menu_fmap2D.cpp index e6078e06c2..4c91b0206a 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, @@ -1796,14 +1787,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; @@ -2598,11 +2594,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_fmap_map.cpp b/src/d/d_menu_fmap_map.cpp index 3d1a9e2523..c500aff0be 100644 --- a/src/d/d_menu_fmap_map.cpp +++ b/src/d/d_menu_fmap_map.cpp @@ -8,6 +8,9 @@ #include "d/d_debug_viewer.h" #include "d/d_menu_fmap_map.h" #include "m_Do/m_Do_graphic.h" +#if TARGET_PC +#include +#endif #include static u8 twoValueLineInterpolation(u8 i_value1, u8 i_value2, f32 i_param) { @@ -494,6 +497,9 @@ void dMenu_FmapMap_c::_delete() { mResTIMG = NULL; } if (mMapImage_p != NULL) { +#if TARGET_PC + GXDestroyCopyTex(mMapImage_p); +#endif JKR_DELETE_ARRAY(mMapImage_p); mMapImage_p = NULL; } 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..ba2b86b760 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, @@ -181,8 +185,22 @@ dMenu_Ring_c::dMenu_Ring_c(JKRExpHeap* i_heap, STControl* i_stick, CSTControl* i field_0x682 = 0xc000; break; } +#if TARGET_PC + mCursorInterpPrevX = 0.0f; + mCursorInterpPrevY = 0.0f; + mCursorInterpCurrX = 0.0f; + mCursorInterpCurrY = 0.0f; + mCursorInterpPrevAngle = 0; + mCursorInterpCurrAngle = 0; + mCursorInterpPrevAngular = false; + mCursorInterpCurrAngular = false; + mCursorInterpInit = false; +#endif 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; @@ -624,7 +642,71 @@ void dMenu_Ring_c::_draw() { } else { drawSelectItem(); drawItem2(); +#if TARGET_PC + f32 simX = 0.0f; + f32 simY = 0.0f; + bool restoreSimPos = false; + if (dusk::frame_interp::is_enabled() && mAlphaRate >= 1.0f) { + simX = mpDrawCursor->getPositionX(); + simY = mpDrawCursor->getPositionY(); + + const bool isAngular = (mStatus == STATUS_MOVE) && !mDirectSelectActive; + + if (dusk::frame_interp::get_ui_tick_pending()) { + mCursorInterpPrevX = mCursorInterpCurrX; + mCursorInterpPrevY = mCursorInterpCurrY; + mCursorInterpPrevAngle = mCursorInterpCurrAngle; + mCursorInterpPrevAngular = mCursorInterpCurrAngular; + + mCursorInterpCurrX = simX; + mCursorInterpCurrY = simY; + mCursorInterpCurrAngle = field_0x66e; + mCursorInterpCurrAngular = isAngular; + + // reset prev = curr for first render pass or + // when angle modes prev/curr differ + // to prevent arrival jitter + if (!mCursorInterpInit || + mCursorInterpPrevAngular != mCursorInterpCurrAngular) { + mCursorInterpPrevX = mCursorInterpCurrX; + mCursorInterpPrevY = mCursorInterpCurrY; + mCursorInterpPrevAngle = mCursorInterpCurrAngle; + mCursorInterpPrevAngular = mCursorInterpCurrAngular; + mCursorInterpInit = true; + } + } + if (mCursorInterpInit) { + const f32 step = dusk::frame_interp::get_interpolation_step(); + if (mCursorInterpPrevAngular && mCursorInterpCurrAngular) { + const s16 delta = mCursorInterpCurrAngle - mCursorInterpPrevAngle; + const s16 lerpedAngle = mCursorInterpPrevAngle + (s16)(delta * step); + + // yoinked from stick_move_proc() + const f32 x = g_ringHIO.mItemRingPosX + FB_WIDTH_BASE / 2 + + mRingRadiusH * cM_ssin(lerpedAngle); + const f32 y = g_ringHIO.mItemRingPosY + FB_HEIGHT_BASE / 2 + + mRingRadiusV * cM_scos(lerpedAngle); + mpDrawCursor->setPos(x, y); + } else { + mpDrawCursor->setPos( + mCursorInterpPrevX + (mCursorInterpCurrX - mCursorInterpPrevX) * step, + mCursorInterpPrevY + (mCursorInterpCurrY - mCursorInterpPrevY) * step + ); + } + restoreSimPos = true; + } + } else { + mCursorInterpInit = false; + } +#endif mpDrawCursor->draw(); +#if TARGET_PC + // prevents offsetting at destination on the next frame + // since stick_wait_proc doesn't call setPos and we clobbered mPositionX/Y + if (restoreSimPos) { + mpDrawCursor->setPos(simX, simY); + } +#endif mpItemExplain->trans(mCenterPosX, mCenterPosY); mpItemExplain->draw((J2DOrthoGraph*)grafPort); drawFlag0(); @@ -1022,6 +1104,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 +1115,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 +1608,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 +1624,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 +1649,11 @@ void dMenu_Ring_c::drawSelectItem() { 0); } } +#if !TARGET_PC field_0x674[i]++; } else { setSelectItemForce(i); +#endif } } } @@ -1562,6 +1664,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 +1674,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..e55b42bc1a 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; + } } } @@ -362,8 +368,22 @@ void dMeterButton_c::draw() { } if (var_r3) { +#ifdef TARGET_PC + if (dusk::frame_interp::get_ui_tick_pending()) { + mWasListen[i] = var_r22; + mWasRepeat[i] = var_r23; + } else { + var_r22 = mWasListen[i]; + var_r23 = mWasRepeat[i]; + } +#endif if (var_r22) { - if (field_0x2e8[i] == 18.0f) { +#ifdef TARGET_PC + if (field_0x2e8[i] == 18.0f && dusk::frame_interp::get_ui_tick_pending()) +#else + if (field_0x2e8[i] == 18.0f) +#endif + { mDoAud_seStart(Z2SE_SY_HINT_BUTTON_BLINK, NULL, 0, 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..4040eb0d7f 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 @@ -303,7 +307,7 @@ static u8 getOutFontNumberType(int param_0) { } } -#if VERSION == VERSION_GCN_PAL +#if TARGET_PC || VERSION == VERSION_GCN_PAL static void setPlayerName(char* i_player_name, u8 param_2) { if (param_2 != 0) { strcpy(i_player_name, dComIfGs_getPlayerName()); @@ -1481,7 +1485,7 @@ bool jmessage_tMeasureProcessor::do_tag(u32 i_tag, void const* i_data, u32 i_siz char buffer[40]; switch (i_tag & 0xFF00FFFF) { case MSGTAG_PLAYER_GENITIV: - #if VERSION == VERSION_GCN_PAL + #if TARGET_PC || VERSION == VERSION_GCN_PAL if (dComIfGs_getPalLanguage() == dSv_player_config_c::LANGUAGE_GERMAN) { setPlayerName(buffer, 1); } else { @@ -1491,7 +1495,7 @@ bool jmessage_tMeasureProcessor::do_tag(u32 i_tag, void const* i_data, u32 i_siz push_word(buffer); return true; case MSGTAG_HORSE_GENITIV: - #if VERSION == VERSION_GCN_PAL + #if TARGET_PC || VERSION == VERSION_GCN_PAL if (dComIfGs_getPalLanguage() == dSv_player_config_c::LANGUAGE_GERMAN) { setHorseName(buffer, 1); } else { @@ -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() { @@ -1978,6 +1987,13 @@ bool jmessage_tSequenceProcessor::do_isReady() { } #endif +#if TARGET_PC + if (dusk::getSettings().game.instantText && mDoCPd_c::getHoldB(0)) { + field_0xb2 = 1; + pReference->setSendTimer(0); + } +#endif + if (dComIfGp_checkMesgBgm()) { bool isItemMusicPlaying = true; if (mDoAud_checkPlayingSubBgmFlag() != Z2BGM_ITEM_GET && @@ -2154,6 +2170,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(); @@ -4242,7 +4270,7 @@ bool jmessage_string_tMeasureProcessor::do_tag(u32 i_tag, void const* i_data, u3 char buffer[40]; switch (i_tag & 0xFF00FFFF) { case MSGTAG_PLAYER_GENITIV: - #if VERSION == VERSION_GCN_PAL + #if TARGET_PC || VERSION == VERSION_GCN_PAL if (dComIfGs_getPalLanguage() == dSv_player_config_c::LANGUAGE_GERMAN) { setPlayerName(buffer, 1); } else { @@ -4253,7 +4281,7 @@ bool jmessage_string_tMeasureProcessor::do_tag(u32 i_tag, void const* i_data, u3 stack_pushCurrent(buffer); break; case MSGTAG_HORSE_GENITIV: - #if VERSION == VERSION_GCN_PAL + #if TARGET_PC || VERSION == VERSION_GCN_PAL if (dComIfGs_getPalLanguage() == dSv_player_config_c::LANGUAGE_GERMAN) { setHorseName(buffer, 1); } else { @@ -4850,7 +4878,7 @@ bool jmessage_string_tRenderingProcessor::do_tag(u32 i_tag, void const* i_data, char buffer[40]; switch (i_tag & 0xFF00FFFF) { case MSGTAG_PLAYER_GENITIV: - #if VERSION == VERSION_GCN_PAL + #if TARGET_PC || VERSION == VERSION_GCN_PAL if (dComIfGs_getPalLanguage() == dSv_player_config_c::LANGUAGE_GERMAN) { setPlayerName(buffer, 1); } else { @@ -4861,7 +4889,7 @@ bool jmessage_string_tRenderingProcessor::do_tag(u32 i_tag, void const* i_data, push_word(buffer); break; case MSGTAG_HORSE_GENITIV: - #if VERSION == VERSION_GCN_PAL + #if TARGET_PC || VERSION == VERSION_GCN_PAL if (dComIfGs_getPalLanguage() == dSv_player_config_c::LANGUAGE_GERMAN) { setHorseName(buffer, 1); } else { diff --git a/src/d/d_msg_object.cpp b/src/d/d_msg_object.cpp index 3676639908..ae0e3d8427 100644 --- a/src/d/d_msg_object.cpp +++ b/src/d/d_msg_object.cpp @@ -24,9 +24,15 @@ #include "f_op/f_op_msg_mng.h" #include #include + +#include "JSystem/JKernel/JKRExpHeap.h" +#include "dusk/version.hpp" #include "m_Do/m_Do_controller_pad.h" #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 +1470,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 +1560,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 +1580,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 +1594,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) { @@ -1648,7 +1645,30 @@ void dMsgObject_c::readMessageGroupLocal(mDoDvdThd_mountXArchive_c** p_arcMount) #else #if TARGET_PC // Original game UB - snprintf(arcName, sizeof(arcName), "/res/Msgus/bmgres%d.arc", msgGroup); + + if (dusk::version::isRegionPal()) { + switch (dComIfGs_getPalLanguage()) { + case dSv_player_config_c::LANGUAGE_GERMAN: + snprintf(arcName, sizeof(arcName), "/res/Msgde/bmgres%d.arc", msgGroup); + break; + case dSv_player_config_c::LANGUAGE_FRENCH: + snprintf(arcName, sizeof(arcName), "/res/Msgfr/bmgres%d.arc", msgGroup); + break; + case dSv_player_config_c::LANGUAGE_SPANISH: + snprintf(arcName, sizeof(arcName), "/res/Msgsp/bmgres%d.arc", msgGroup); + break; + case dSv_player_config_c::LANGUAGE_ITALIAN: + snprintf(arcName, sizeof(arcName), "/res/Msgit/bmgres%d.arc", msgGroup); + break; + default: + snprintf(arcName, sizeof(arcName), "/res/Msguk/bmgres%d.arc", msgGroup); + } + } else if (dusk::version::isRegionJpn()) { + snprintf(arcName, sizeof(arcName), "/res/Msgjp/bmgres%d.arc", msgGroup); + } else { + snprintf(arcName, sizeof(arcName), "/res/Msgus/bmgres%d.arc", msgGroup); + } + #else sprintf(arcName, "/res/Msgus/bmgres%d.arc", msgGroup); #endif 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..c701ea0b2b 100644 --- a/src/d/d_msg_scrn_howl.cpp +++ b/src/d/d_msg_scrn_howl.cpp @@ -20,6 +20,7 @@ #include "Z2AudioLib/Z2WolfHowlMgr.h" #if TARGET_PC +#include "dusk/frame_interpolation.h" #include "dusk/settings.h" #endif @@ -580,9 +581,14 @@ void dMsgScrnHowl_c::drawWave() { f17 = local_60; f18 = local_64; } else { - field_0x2134++; - if (field_0x2134 > 30) { - field_0x2134 = 0; +#if TARGET_PC + if (dusk::frame_interp::get_ui_tick_pending()) +#endif + { + field_0x2134++; + if (field_0x2134 > 30) { + field_0x2134 = 0; + } } if (field_0x2134 < 15) { local_dc = field_0x2134 / 15.0f; @@ -607,17 +613,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 +744,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 +850,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..7bbe8b3d70 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. @@ -309,11 +334,11 @@ void dMsgUnit_c::setTag(int i_type, int i_value, char* o_buffer, bool param_4) { vals[1] = ((dMsgUnit_inf1_section_t*)pInfoBlock)->entries[i_type].endFrame; #endif -#if REGION_PAL +#if TARGET_PC || REGION_PAL if (i_value == 1 || (dComIfGs_getPalLanguage() == dSv_player_config_c::LANGUAGE_FRENCH && i_value == 0)) { -#elif !REGION_USA +#elif !REGION_USA // Dusk TODO: What? This checks for Spanish when *not* PAL or USA? if (i_value == 1 || (dComIfGs_getPalLanguage() == dSv_player_config_c::LANGUAGE_SPANISH && i_value == 0)) { diff --git a/src/d/d_name.cpp b/src/d/d_name.cpp index 120bcff752..1f3ccbb1d7 100644 --- a/src/d/d_name.cpp +++ b/src/d/d_name.cpp @@ -8,9 +8,15 @@ #include "m_Do/m_Do_controller_pad.h" #include #include + #include "JSystem/J2DGraph/J2DAnmLoader.h" +#include "dusk/version.hpp" #include "f_op/f_op_msg_mng.h" +static bool isPalOrJpn() { + return dusk::version::isRegionPal() || dusk::version::isRegionJpn(); +} + static const char* l_mojiHira[65] = { "あ", "い", "う", "え", "お", "か", "き", "く", "け", "こ", "さ", "し", "す", "せ", "そ", "た", "ち", "つ", "て", "と", "な", "に", "ぬ", "ね", "の", "は", @@ -66,15 +72,31 @@ static const char* l_mojiEisu[65] = { "X", "k", "x", ",", "L", "Y", "l", "y", ".", "M", "Z", "m", "z", " ", }; -#if REGION_PAL -static char* l_mojiEisuPal_1[65] = { +#if TARGET_PC +// The game normally mutates this string list to fill in the real character codes. +// That can't work on a modern platform, so instead I've filled them out ahead of time. +static const char* l_mojiEisuPal_1[65] = { + "A", "N", "\xC0", "\xCF", "1", "B", "O", "\xC1", "\xD0", "2", "C", "P", "\xC2", "\xD1", "3", "D", "Q", + "\xC3", "\xD2", "4", "E", "R", "\xC4", "\xD3", "5", "F", "S", "\xC5", "\xD4", "6", "G", "T", "\xC6", "\xD5", + "7", "H", "U", "\xC7", "\xD6", "8", "I", "V", "\xC8", "\xD7", "9", "J", "W", "\xC9", "\xD8", "0", "K", + "X", "\xCA", "\xD9", ",", "L", "Y", "\xCB", "\xDA", ".", "M", "Z", "\xCC", "\xDB", " ", +}; + +static const char* l_mojiEisuPal_2[65] = { + "a", "n", "\xE0", "\xEF", "1", "b", "o", "\xE1", "\xF0", "2", "c", "p", "\xE2", "\xF1", "3", "d", "q", + "\xE3", "\xF2", "4", "e", "r", "\xE4", "\xF3", "5", "f", "s", "\xE5", "\xF4", "6", "g", "t", "\xE6", + "\xF5", "7", "h", "u", "\xE7", "\xF6", "8", "i", "v", "\xE8", "\xF7", "9", "j", "w", "\xE9", "\xF8", "0", + "k", "x", "\xEA", "\xF9", ",", "l", "y", "\xEB", "\xFA", ".", "m", "z", "\xEC", "\xFB", " ", +}; +#elif REGION_PAL +static const char* l_mojiEisuPal_1[65] = { "A", "N", "AA", "BB", "1", "B", "O", "CC", "DD", "2", "C", "P", "EE", "FF", "3", "D", "Q", "GG", "HH", "4", "E", "R", "II", "JJ", "5", "F", "S", "KK", "LL", "6", "G", "T", "MM", "NN", "7", "H", "U", "OO", "PP", "8", "I", "V", "QQ", "RR", "9", "J", "W", "SS", "TT", "0", "K", "X", "UU", "VV", ",", "L", "Y", "WW", "XX", ".", "M", "Z", "YY", "ZZ", " ", }; -static char* l_mojiEisuPal_2[65] = { +static const char* l_mojiEisuPal_2[65] = { "a", "n", "aa", "bb", "1", "b", "o", "cc", "dd", "2", "c", "p", "ee", "ff", "3", "d", "q", "gg", "hh", "4", "e", "r", "ii", "jj", "5", "f", "s", "kk", "ll", "6", "g", "t", "mm", "nn", "7", "h", "u", "oo", "pp", "8", "i", "v", "qq", "rr", "9", "j", "w", "ss", "tt", "0", @@ -82,6 +104,16 @@ static char* l_mojiEisuPal_2[65] = { }; #endif +#if TARGET_PC +// ' ' (full-width space) +#define SPACE_MAYBE_FULL (dusk::version::isRegionJpn() ? '\x81\x40' : ' ') +#elif REGION_JPN +// ' ' (full-width space) +#define SPACE_MAYBE_FULL '\x81\x40' +#else +#define SPACE_MAYBE_FULL ' ' +#endif + static dNm_HIO_c g_nmHIO; typedef void (dName_c::*selProcFunc)(void); @@ -163,13 +195,18 @@ void dName_c::init() { field_0x2ac = mSelProc; field_0x2ad = mSelProc; field_0x2ae = field_0x2ac; - #if REGION_PAL || REGION_JPN + #if TARGET_PC + mMojiSet = isPalOrJpn() ? MOJI_HIRA : MOJI_EIGO; + #elif REGION_PAL || REGION_JPN mMojiSet = MOJI_HIRA; #else mMojiSet = MOJI_EIGO; #endif mPrevMojiSet = 255; - #if REGION_PAL || REGION_JPN + #if TARGET_PC + mSelMenu = isPalOrJpn() ? MENU_HIRA : MENU_END; + mPrevSelMenu = isPalOrJpn() ? MENU_HIRA : MENU_END; + #elif REGION_PAL || REGION_JPN mSelMenu = MENU_HIRA; mPrevSelMenu = MENU_HIRA; #else @@ -187,7 +224,8 @@ void dName_c::initial() { mNextNameStr[0] = 0; } - #if REGION_PAL || REGION_JPN + #if TARGET_PC || REGION_PAL || REGION_JPN + IF_DUSK_BLOCK(isPalOrJpn()) if (mSelProc == PROC_MOJI_SELECT) { mMenuIcon[mMojiSet]->scale(g_nmHIO.mMenuScale, g_nmHIO.mMenuScale); mMenuText[mMojiSet]->setWhite(JUtility::TColor(0xC8, 0xC8, 0xC8, 0xFF)); @@ -196,6 +234,7 @@ void dName_c::initial() { mMenuText[mPrevMojiSet]->setWhite(JUtility::TColor(0x96, 0x96, 0x96, 0xFF)); } } + IF_DUSK_BLOCK_END #endif } @@ -230,8 +269,8 @@ void dName_c::_move() { stick->checkTrigger(); (this->*SelProc[mSelProc])(); - #if REGION_PAL || REGION_JPN - if (mDoCPd_c::getTrigY(PAD_1)) { + #if TARGET_PC || REGION_PAL || REGION_JPN + if (IF_DUSK(isPalOrJpn() &&) mDoCPd_c::getTrigY(PAD_1)) { mDoAud_seStart(Z2SE_SY_DUMMY, 0, 0, 0); mPrevMojiSet = mMojiSet; mMojiSet++; @@ -245,8 +284,8 @@ void dName_c::_move() { mojiListChange(); } else { #endif - #if REGION_JPN - if (mDoCPd_c::getTrigX(PAD_1)) { + #if TARGET_PC || REGION_JPN + if (IF_DUSK(dusk::version::isRegionJpn() &&) mDoCPd_c::getTrigX(PAD_1)) { if (mCurPos != 0) { if (mojiChange(mCurPos - 1) == 1) { mDoAud_seStart(Z2SE_SY_DUMMY, 0, 0, 0); @@ -283,12 +322,14 @@ void dName_c::_move() { backSpace(); } } else if (mDoCPd_c::getTrigStart(PAD_1)) { +#define EIGO_OR_END DUSK_IF_ELSE((dusk::version::isRegionPal() ? MENU_EIGO : MENU_END), MENU_END) + #if REGION_PAL if ((mSelProc != PROC_MENU_SELECT || mSelMenu != MENU_EIGO) && (mSelProc == PROC_MENU_SELECT || mSelProc == PROC_MOJI_SELECT)) { #else - if ((mSelProc != PROC_MENU_SELECT || mSelMenu != MENU_END) && + if ((mSelProc != PROC_MENU_SELECT || mSelMenu != EIGO_OR_END) && (mSelProc == PROC_MENU_SELECT || mSelProc == PROC_MOJI_SELECT)) { #endif @@ -297,7 +338,7 @@ void dName_c::_move() { #if REGION_PAL mSelMenu = MENU_EIGO; #else - mSelMenu = MENU_END; + mSelMenu = EIGO_OR_END; #endif switch (mSelProc) { @@ -314,10 +355,10 @@ void dName_c::_move() { } } } - #if REGION_JPN + #if TARGET_PC || REGION_JPN } #endif - #if REGION_PAL || REGION_JPN + #if TARGET_PC || REGION_PAL || REGION_JPN } #endif @@ -327,10 +368,9 @@ void dName_c::_move() { int dName_c::nameCheck() { for (int i = 8, len = 7; i > 0; i--) { #if REGION_JPN - // ' ' (full-width space) if (mChrInfo[len].mCharacter != ' ' && mChrInfo[len].mCharacter != '\x81\x40') { #else - if (mChrInfo[len].mCharacter != ' ') { + if (mChrInfo[len].mCharacter != ' ' IF_DUSK(&& (!dusk::version::isRegionJpn() || mChrInfo[len].mCharacter != '\x81\x40'))) { #endif return len + 1; } @@ -344,8 +384,8 @@ void dName_c::playNameSet(int nameLength) { char* str = mInputStr; for (int i = 0; i < nameLength; i++) { - #if REGION_JPN - if (mChrInfo[i].mMojiSet == 2) { + #if TARGET_PC || REGION_JPN + if (!dusk::version::isRegionJpn() || mChrInfo[i].mMojiSet == 2) { *str = mChrInfo[i].mCharacter; str += 1; } else { @@ -727,7 +767,34 @@ int dName_c::getMoji() { int result = -1; const char* moji; - #if REGION_PAL + #if TARGET_PC + if (dusk::version::isRegionPal()) { + switch (mMojiSet) { + case MOJI_HIRA: + moji = l_mojiEisuPal_1[mCharRow + mCharColumn * 5]; + break; + case MOJI_KATA: + moji = l_mojiEisuPal_2[mCharRow + mCharColumn * 5]; + break; + default: + abort(); + } + } else { + switch (mMojiSet) { + case MOJI_HIRA: + moji = l_mojiHira[mCharRow + mCharColumn * 5]; + break; + case MOJI_KATA: + moji = l_mojikata[mCharRow + mCharColumn * 5]; + break; + case MOJI_EIGO: + moji = l_mojiEisu[mCharRow + mCharColumn * 5]; + break; + default: + abort(); + } + } + #elif REGION_PAL switch (mMojiSet) { case MOJI_HIRA: moji = l_mojiEisuPal_1[mCharRow + mCharColumn * 5]; @@ -750,7 +817,17 @@ int dName_c::getMoji() { } #endif - #if REGION_JPN + #if TARGET_PC + if (dusk::version::isRegionJpn()) { + if (*(u8*)moji >> 4 == 0x8 || *(u8*)moji >> 4 == 0x9) { + result = *(u16*)moji; + } else { + result = *moji; + } + } else { + result = *moji; + } + #elif REGION_JPN if (*(u8*)moji >> 4 == 0x8 || *(u8*)moji >> 4 == 0x9) { result = *(u16*)moji; } else { @@ -763,6 +840,14 @@ int dName_c::getMoji() { return result; } +#if TARGET_PC +#define CHAR_TRUNC(val) (dusk::version::isRegionPal() ? val & 0xFF : val) +#elif REGION_PAL +#define CHAR_TRUNC(val) (val & 0xFF) +#else +#define CHAR_TRUNC(val) val +#endif + void dName_c::setMoji(int moji) { if (mCurPos == 8 || nameCheck() == 8) { mDoAud_seStart(Z2SE_SYS_ERROR, NULL, 0, 0); @@ -771,24 +856,14 @@ void dName_c::setMoji(int moji) { s32 notEmpty = false; for (int i = mCurPos; i < 8; i++) { - #if REGION_JPN - // ' ' (full-width space) - if (mChrInfo[i].mCharacter != '\x81\x40') { - #else - if (mChrInfo[i].mCharacter != ' ') { - #endif + if (mChrInfo[i].mCharacter != SPACE_MAYBE_FULL) { notEmpty = true; break; } } if (notEmpty) { - #if REGION_JPN - // ' ' (full-width space) - if (mChrInfo[7].mCharacter == '\x81\x40') { - #else - if (mChrInfo[7].mCharacter == ' ') { - #endif + if (mChrInfo[7].mCharacter == SPACE_MAYBE_FULL) { for (int i = 6; i >= mCurPos; i--) { mChrInfo[i + 1] = mChrInfo[i]; } @@ -797,11 +872,7 @@ void dName_c::setMoji(int moji) { mChrInfo[mCurPos].mRow = mCharRow; mChrInfo[mCurPos].mMojiSet = mMojiSet; mChrInfo[mCurPos].field_0x3 = 1; - #if REGION_PAL - mChrInfo[mCurPos].mCharacter = moji & 0xFF; - #else - mChrInfo[mCurPos].mCharacter = moji; - #endif + mChrInfo[mCurPos].mCharacter = CHAR_TRUNC(moji); if (mCurPos != 8) { mLastCurPos = mCurPos; @@ -814,11 +885,7 @@ void dName_c::setMoji(int moji) { mChrInfo[mCurPos].mRow = mCharRow; mChrInfo[mCurPos].mMojiSet = mMojiSet; mChrInfo[mCurPos].field_0x3 = 1; - #if REGION_PAL - mChrInfo[mCurPos].mCharacter = moji & 0xFF; - #else - mChrInfo[mCurPos].mCharacter = moji; - #endif + mChrInfo[mCurPos].mCharacter = CHAR_TRUNC(moji); if (mCurPos != 8) { mLastCurPos = mCurPos; @@ -844,13 +911,8 @@ void dName_c::setNameText() { "CR\x1b" "CC[000000]\x1bGM[0]%c\x1bHM\x1b" "CC[ffffff]\x1bGM[0]%c", - #if REGION_PAL - (u8)mChrInfo[i].mCharacter & 0xFF, - (u8)mChrInfo[i].mCharacter & 0xFF - #else - (u8)mChrInfo[i].mCharacter, - (u8)mChrInfo[i].mCharacter - #endif + CHAR_TRUNC((u8)mChrInfo[i].mCharacter), + CHAR_TRUNC((u8)mChrInfo[i].mCharacter) ); #if REGION_JPN } else { @@ -889,7 +951,29 @@ void dName_c::nameCursorMove() { void dName_c::selectCursorMove() { int idx; - #if REGION_PAL + #if TARGET_PC + if (dusk::version::isRegionPal()) { + if (mCharColumn < 3) { + idx = 0; + } else if (mCharColumn < 6) { + idx = 1; + } else if (mCharColumn >= 6) { + idx = 2; + } + } else if (dusk::version::isRegionJpn()) { + if (mCharColumn < 3) { + idx = 0; + } else if (mCharColumn < 6) { + idx = 1; + } else if (mCharColumn < 8) { + idx = 2; + } else if (mCharColumn >= 8) { + idx = 3; + } + } else { + idx = 3; + } + #elif REGION_PAL if (mCharColumn < 3) { idx = 0; } else if (mCharColumn < 6) { @@ -930,7 +1014,36 @@ void dName_c::selectCursorMove() { void dName_c::menuCursorPosSet() { mPrevSelMenu = mSelMenu; - #if REGION_PAL + #if TARGET_PC + if (dusk::version::isRegionPal()) { + if (mCharColumn < 3) { + mSelMenu = MENU_HIRA; + } else if (mCharColumn < 6) { + mSelMenu = MENU_KATA; + } else if (mCharColumn >= 6) { + mSelMenu = MENU_EIGO; + } + } else if (dusk::version::isRegionJpn()) { + if (mCharColumn < 3) { + mSelMenu = MENU_HIRA; + return; + } + if (mCharColumn < 6) { + mSelMenu = MENU_KATA; + return; + } + if (mCharColumn < 8) { + mSelMenu = MENU_EIGO; + return; + } + if (mCharColumn >= 8) { + mSelMenu = MENU_END; + return; + } + } else { + mSelMenu = MENU_END; + } + #elif REGION_PAL if (mCharColumn < 3) { mSelMenu = MENU_HIRA; } else if (mCharColumn < 6) { @@ -961,28 +1074,28 @@ void dName_c::menuCursorPosSet() { } void dName_c::MenuSelect() { - #if REGION_PAL || REGION_JPN - if (stick->checkRightTrigger()) { + #if TARGET_PC || REGION_PAL || REGION_JPN + if (isPalOrJpn() && stick->checkRightTrigger()) { mDoAud_seStart(Z2SE_SY_CURSOR_OPTION, NULL, 0, 0); mPrevSelMenu = mSelMenu; mSelMenu++; #if REGION_PAL if (mSelMenu > MENU_EIGO) { #else - if (mSelMenu > MENU_END) { + if (mSelMenu > EIGO_OR_END) { #endif mSelMenu = MENU_HIRA; } MenuSelectAnmInit(); mSelProc = PROC_MENU_SEL_ANM; - } else if (stick->checkLeftTrigger()) { + } else if (isPalOrJpn() && stick->checkLeftTrigger()) { mDoAud_seStart(Z2SE_SY_CURSOR_OPTION, NULL, 0, 0); mPrevSelMenu = mSelMenu; if (mSelMenu == MENU_HIRA) { #if REGION_JPN mSelMenu = MENU_END; #else - mSelMenu = MENU_EIGO; + mSelMenu = dusk::version::isRegionJpn() ? MENU_END : MENU_EIGO; #endif } else { mSelMenu--; @@ -1009,7 +1122,7 @@ void dName_c::MenuSelect() { #if REGION_PAL if (mSelMenu == MENU_EIGO) { #else - if (mSelMenu == MENU_END) { + if (mSelMenu == EIGO_OR_END) { #endif if (nameCheck() != 0) { mDoAud_seStart(Z2SE_SY_NAME_OK, NULL, 0, 0); @@ -1024,7 +1137,7 @@ void dName_c::MenuSelect() { #if REGION_PAL if (mSelMenu == MENU_EIGO) { #else - if (mSelMenu == MENU_END) { + if (mSelMenu == EIGO_OR_END) { #endif if (nameCheck() != 0) { mDoAud_seStart(Z2SE_SY_NAME_OK, NULL, 0, 0); @@ -1067,9 +1180,11 @@ void dName_c::MenuSelectAnm2() { if (canMove == true) { if (prevMenu_i != mojiSet_i) { mMenuText[prevMenu_i]->setWhite(JUtility::TColor(0x96, 0x96, 0x96, 0xFF)); - #if REGION_PAL || REGION_JPN + #if TARGET_PC || REGION_PAL || REGION_JPN + IF_DUSK_BLOCK(isPalOrJpn()) mMenuIcon[mojiSet_i]->scale(g_nmHIO.mMenuScale, g_nmHIO.mMenuScale); mMenuText[mojiSet_i]->setWhite(JUtility::TColor(0xC8, 0xC8, 0xC8, 0xFF)); + IF_DUSK_BLOCK_END #endif } selectCursorMove(); @@ -1081,6 +1196,11 @@ void dName_c::MenuSelectAnm2() { void dName_c::MenuSelectAnm3() {} void dName_c::menuAbtnSelect() { +#if TARGET_PC + if (dusk::version::isRegionPal() && mSelMenu == MENU_EIGO) { + goto pal_eigo; + } +#endif switch (mSelMenu) { case MENU_HIRA: case MENU_KATA: @@ -1098,6 +1218,7 @@ void dName_c::menuAbtnSelect() { #else case MENU_END: #endif + IF_DUSK(pal_eigo:) int nameNum = nameCheck(); if (nameNum != 0) { playNameSet(nameNum); @@ -1116,44 +1237,33 @@ void dName_c::backSpace() { if (mCurPos != 0) { mDoAud_seStart(Z2SE_SY_NAME_DELETE, NULL, 0, 0); - #if REGION_JPN - // ' ' (full-width space) - if (mCurPos == 8 && mChrInfo[7].mCharacter != '\x81\x40') { - #else - if (mCurPos == 8 && mChrInfo[7].mCharacter != ' ') { - #endif + if (mCurPos == 8 && mChrInfo[7].mCharacter != SPACE_MAYBE_FULL) { mChrInfo[7].mColumn = 7; mChrInfo[7].mRow = 1; - #if REGION_PAL || REGION_JPN + #if TARGET_PC + mChrInfo[7].mMojiSet = isPalOrJpn() ? MOJI_HIRA : MOJI_EIGO; + #elif REGION_PAL || REGION_JPN mChrInfo[7].mMojiSet = MOJI_HIRA; #else mChrInfo[7].mMojiSet = MOJI_EIGO; #endif mChrInfo[7].field_0x3 = 1; - #if REGION_JPN - // ' ' (full-width space) - mChrInfo[7].mCharacter = '\x81\x40'; - #else - mChrInfo[7].mCharacter = ' '; - #endif + mChrInfo[7].mCharacter = SPACE_MAYBE_FULL; } else { for (int i = mCurPos - 1; i < 7; i++) { mChrInfo[i] = mChrInfo[i + 1]; } mChrInfo[7].mColumn = 7; mChrInfo[7].mRow = 1; - #if REGION_PAL || REGION_JPN +#if TARGET_PC + mChrInfo[7].mMojiSet = isPalOrJpn() ? MOJI_HIRA : MOJI_EIGO; +#elif REGION_PAL || REGION_JPN mChrInfo[7].mMojiSet = MOJI_HIRA; #else mChrInfo[7].mMojiSet = MOJI_EIGO; #endif mChrInfo[7].field_0x3 = 1; - #if REGION_JPN - // ' ' (full-width space) - mChrInfo[7].mCharacter = '\x81\x40'; - #else - mChrInfo[7].mCharacter = ' '; - #endif + mChrInfo[7].mCharacter = SPACE_MAYBE_FULL; } setNameText(); @@ -1164,7 +1274,31 @@ void dName_c::backSpace() { } void dName_c::mojiListChange() { - #if REGION_PAL + #if TARGET_PC + const char** mojiSet; + if (dusk::version::isRegionPal()) { + switch (mMojiSet) { + case MOJI_HIRA: + mojiSet = l_mojiEisuPal_1; + break; + case MOJI_KATA: + mojiSet = l_mojiEisuPal_2; + break; + } + } else { + switch (mMojiSet) { + case MOJI_HIRA: + mojiSet = l_mojiHira; + break; + case MOJI_KATA: + mojiSet = l_mojikata; + break; + case MOJI_EIGO: + mojiSet = l_mojiEisu; + break; + } + } + #elif REGION_PAL char** mojiSet; switch (mMojiSet) { @@ -1212,7 +1346,8 @@ void dName_c::mojiListChange() { strcpy(mMojiText[i], buf); } - #if REGION_PAL || REGION_JPN + #if TARGET_PC || REGION_PAL || REGION_JPN + IF_DUSK_BLOCK(isPalOrJpn()) if (mSelProc == PROC_MOJI_SELECT) { mMenuIcon[mMojiSet]->scale(g_nmHIO.mMenuScale, g_nmHIO.mMenuScale); mMenuText[mMojiSet]->setWhite(JUtility::TColor(0xC8, 0xC8, 0xC8, 0xFF)); @@ -1221,6 +1356,7 @@ void dName_c::mojiListChange() { mMenuText[mPrevMojiSet]->setWhite(JUtility::TColor(0x96, 0x96, 0x96, 0xFF)); } } + IF_DUSK_BLOCK_END #endif } @@ -1241,9 +1377,11 @@ void dName_c::menuCursorMove2() { if (menu_i != mojiSet_i) { mMenuIcon[menu_i]->scale(g_nmHIO.mMenuScale, g_nmHIO.mMenuScale); mMenuText[menu_i]->setWhite(JUtility::TColor(0xC8, 0xC8, 0xC8, 0xFF)); - #if REGION_PAL || REGION_JPN + #if TARGET_PC || REGION_PAL || REGION_JPN + IF_DUSK_BLOCK(isPalOrJpn()) mMenuIcon[mojiSet_i]->scale(1.0f, 1.0f); mMenuText[mojiSet_i]->setWhite(JUtility::TColor(0x96, 0x96, 0x96, 0xFF)); + IF_DUSK_BLOCK_END #endif } @@ -1265,7 +1403,9 @@ void dName_c::selectCursorPosSet(int row) { mCharColumn = 3; break; case MENU_EIGO: - #if REGION_PAL + #if TARGET_PC + mCharColumn = dusk::version::isRegionPal() ? 8 : 6; + #elif REGION_PAL mCharColumn = 8; #else mCharColumn = 6; @@ -1288,7 +1428,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] = { @@ -1477,9 +1621,11 @@ void dName_c::screenSet() { #endif } - #if !(REGION_PAL || REGION_JPN) + #if TARGET_PC || !(REGION_PAL || REGION_JPN) + IF_DUSK_BLOCK(!isPalOrJpn()) mMenuIcon[0]->hide(); mMenuIcon[1]->hide(); + IF_DUSK_BLOCK_END #endif mMojiPane = nameIn.NameInScr->search(MULTI_CHAR('moji_n')); @@ -1497,25 +1643,28 @@ void dName_c::screenSet() { ((J2DTextBox*)nameTagPane[i])->setFont(nameIn.font); ((J2DTextBox*)nameTagPane[i])->setString(72, ""); ((J2DTextBox*)nameTagPane[i])->setWhite(JUtility::TColor(0xC8, 0xC8, 0xC8, 0xFF)); - #if REGION_PAL + #if TARGET_PC || REGION_PAL + IF_DUSK_BLOCK(dusk::version::isRegionPal()) ((J2DTextBox*)nameTagPane[i])->resize(24.0f, 23.0f); + IF_DUSK_BLOCK_END #endif mNameText[i] = ((J2DTextBox*)nameTagPane[i])->getStringPtr(); } - #if REGION_PAL + #if REGION_PAL // DUSK version note: this code mutates strings. We just edit the table. + IF_DUSK_BLOCK(dusk::version::isRegionPal()) int idx = 2; - static u8 palMoji00[13] = { + static const u8 palMoji00[13] = { 0xC0, 0xC1, 0xC2, 0xC4, 0xC6, 0xC7, 0xC8, 0xC9, 0xCA, 0xCB, 0xCC, 0xCD, 0xCE, }; - static u8 palMoji01[13] = { + static const u8 palMoji01[13] = { 0xCF, 0xD0, 0xD1, 0xD2, 0xD3, 0xD4, 0xD6, 0x8C, 0xD9, 0xDA, 0xDB, 0xDC, 0x2D, }; - static u8 palMoji10[13] = { + static const u8 palMoji10[13] = { 0xE0, 0xE1, 0xE2, 0xE4, 0xE6, 0xE7, 0xE8, 0xE9, 0xEA, 0xEB, 0xEC, 0xED, 0xEE, }; - static u8 palMoji11[13] = { + static const u8 palMoji11[13] = { 0xEF, 0xF0, 0xF1, 0xF2, 0xF3, 0xF4, 0xF6, 0x9C, 0xF9, 0xFA, 0xFB, 0xFC, 0xDF, }; @@ -1532,6 +1681,7 @@ void dName_c::screenSet() { l_mojiEisuPal_2[idx + 1][0] = palMoji11[i]; l_mojiEisuPal_2[idx + 1][1] = 0; } + IF_DUSK_BLOCK_END #endif mCharColumn = 0; @@ -1540,11 +1690,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); @@ -1574,18 +1720,15 @@ void dName_c::displayInit() { mNameCursor[i]->hide(); mChrInfo[i].mColumn = 7; mChrInfo[i].mRow = 1; - #if REGION_PAL || REGION_JPN + #if TARGET_PC + mChrInfo[i].mMojiSet = isPalOrJpn() ? MOJI_HIRA : MOJI_EIGO; + #elif REGION_PAL || REGION_JPN mChrInfo[i].mMojiSet = MOJI_HIRA; #else mChrInfo[i].mMojiSet = MOJI_EIGO; #endif mChrInfo[i].field_0x3 = 1; - #if REGION_JPN - // ' ' (full-width space) - mChrInfo[i].mCharacter = '\x81\x40'; - #else - mChrInfo[i].mCharacter = ' '; - #endif + mChrInfo[i].mCharacter = SPACE_MAYBE_FULL; } mIsInputEnd = false; @@ -1596,7 +1739,63 @@ void dName_c::NameStrSet() { int i = 0; while (*moji != 0) { - #if REGION_PAL + #if TARGET_PC + if (dusk::version::isRegionPal()) { + mChrInfo[i].mCharacter = static_cast(*moji); + + for (int j = 0; j < 65; j++) { + if (mChrInfo[i].mCharacter == *(u8*)l_mojiEisuPal_1[j] || + mChrInfo[i].mCharacter == *(u16*)l_mojiEisuPal_2[j]) + { + mChrInfo[i].mColumn = j / 5; + mChrInfo[i].mRow = j % 5; + mChrInfo[i].mMojiSet = MOJI_HIRA; + break; + } + } + moji++; + i++; + } else { + if (*(u8*)moji >> 4 == 8 || *(u8*)moji >> 4 == 9) { + mChrInfo[i].mCharacter = *(u16*)moji; + + for (int j = 0; j < 65; j++) { + if (mChrInfo[i].mCharacter == *(u16*)l_mojiHira[j] || + mChrInfo[i].mCharacter == *(u16*)l_mojiHira2[j] || + mChrInfo[i].mCharacter == *(u16*)l_mojiHira3[j]) + { + mChrInfo[i].mColumn = j / 5; + mChrInfo[i].mRow = j % 5; + mChrInfo[i].mMojiSet = MOJI_HIRA; + break; + } else if (mChrInfo[i].mCharacter == *(u16*)l_mojikata[j] || + mChrInfo[i].mCharacter == *(u16*)l_mojikata2[j] || + mChrInfo[i].mCharacter == *(u16*)l_mojikata3[j]) + { + mChrInfo[i].mColumn = j / 5; + mChrInfo[i].mRow = j % 5; + mChrInfo[i].mMojiSet = MOJI_KATA; + break; + } + } + moji += 2; + i++; + } else { + mChrInfo[i].mCharacter = *moji; + + for (int j = 0; j < 65; j++) { + if (mChrInfo[i].mCharacter == *(u8*)l_mojiEisu[j]) { + mChrInfo[i].mColumn = j / 5; + mChrInfo[i].mRow = j % 5; + mChrInfo[i].mMojiSet = MOJI_EIGO; + break; + } + } + moji++; + i++; + } + } + #elif REGION_PAL mChrInfo[i].mCharacter = static_cast(*moji); for (int j = 0; j < 65; j++) { @@ -1669,7 +1868,9 @@ s32 dName_c::getMenuPosIdx(u8 selPos) { result = 1; break; case 2: - #if REGION_PAL + #if TARGET_PC + result = dusk::version::isRegionPal() ? 3 : 2; + #elif REGION_PAL result = 3; #else result = 2; 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_particle.cpp b/src/d/d_particle.cpp index 248424e9a2..9b89afc7b1 100644 --- a/src/d/d_particle.cpp +++ b/src/d/d_particle.cpp @@ -1972,7 +1972,12 @@ void dPa_light8PcallBack::draw(JPABaseEmitter* param_1, JPABaseParticle* param_2 JGeometry::TVec3 local_154; JGeometry::TVec3 local_160; JGeometry::TVec3 local_16c; - dPa_setWindPower(param_2); +#if TARGET_PC + if (dusk::frame_interp::is_sim_frame()) +#endif + { + dPa_setWindPower(param_2); + } MTXIdentity(local_60); MTXIdentity(auStack_90); param_2->getBaseAxis(&local_10c); diff --git a/src/d/d_resorce.cpp b/src/d/d_resorce.cpp index 0bf537bfd7..bb982c4afb 100644 --- a/src/d/d_resorce.cpp +++ b/src/d/d_resorce.cpp @@ -102,11 +102,7 @@ static void setIndirectTex(J3DModelData* i_modelData) { texture->setResTIMG(i, *mDoGph_gInf_c::getFrameBufferTimg()); } if (memcmp(textureName, "Zbuffer", 8) == 0) { -#if !TARGET_PC texture->setResTIMG(i, *mDoGph_gInf_c::getZbufferTimg()); -#else - DuskLog.warn("Zbuffer texture binding not yet supported"); -#endif } } } @@ -296,7 +292,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_logo.cpp b/src/d/d_s_logo.cpp index f04df0fa39..cc5d0114a4 100644 --- a/src/d/d_s_logo.cpp +++ b/src/d/d_s_logo.cpp @@ -24,6 +24,7 @@ #include "JSystem/JUtility/JUTConsole.h" #include "dusk/logging.h" +#include "dusk/version.hpp" #if !PLATFORM_GCN #include @@ -44,7 +45,12 @@ struct homeBtnData { }; #endif -#if VERSION == VERSION_SHIELD +#if TARGET_PC +using namespace dusk::version; + +#define LOGO_ARC versionSelect({{GameVersion::GcnJpn, "Logo"}, {GameVersion::GcnPal, "LogoPal"}}, "LogoUs") +#define MSG_PATH versionSelect({{GameVersion::GcnJpn, "/res/Msgjp/bmgres.arc"}}, "/res/Msgus/bmgres.arc") +#elif VERSION == VERSION_SHIELD #define LOGO_ARC "LogoUs" #define MSG_PATH "/res/Msgcn/bmgres.arc" #elif VERSION == VERSION_GCN_JPN @@ -789,7 +795,13 @@ dScnLogo_c::~dScnLogo_c() { JKR_DELETE(mProgressiveNo); JKR_DELETE(mProgressiveSel); - #if VERSION == VERSION_GCN_PAL + #if TARGET_PC + if (getGameVersion() == GameVersion::GcnPal) { + mpPalLogoResCommand->getArchive()->removeResourceAll(); + mpPalLogoResCommand->getArchive()->unmount(); + mpPalLogoResCommand->destroy(); + } + #elif VERSION == VERSION_GCN_PAL mpPalLogoResCommand->getArchive()->removeResourceAll(); mpPalLogoResCommand->getArchive()->unmount(); mpPalLogoResCommand->destroy(); @@ -951,7 +963,8 @@ static int phase_0(dScnLogo_c* i_this) { JUT_ASSERT(1528, i_this->mLogo01Heap != NULL); JKRHEAP_NAME(i_this->mLogo01Heap, "Logo01"); - #if VERSION == VERSION_GCN_PAL + #if TARGET_PC || VERSION == VERSION_GCN_PAL + IF_DUSK_BLOCK(getGameVersion() == GameVersion::GcnPal) switch (i_this->getPalLanguage()) { case 1: i_this->mpPalLogoResCommand = mDoDvdThd_mountArchive_c::create("/res/Layout/LogoPalGm.arc", 0, NULL); @@ -970,6 +983,7 @@ static int phase_0(dScnLogo_c* i_this) { i_this->mpPalLogoResCommand = mDoDvdThd_mountArchive_c::create("/res/Layout/LogoPalUk.arc", 0, NULL); break; } + IF_DUSK_BLOCK_END #endif #if PLATFORM_WII || PLATFORM_SHIELD @@ -990,7 +1004,8 @@ static int phase_1(dScnLogo_c* i_this) { } #endif - #if VERSION == VERSION_GCN_PAL + #if TARGET_PC || VERSION == VERSION_GCN_PAL + IF_DUSK_BLOCK(getGameVersion() == GameVersion::GcnPal) if (!mDoDvdThd::SyncWidthSound) { return cPhs_INIT_e; } @@ -998,6 +1013,7 @@ static int phase_1(dScnLogo_c* i_this) { if (!i_this->mpPalLogoResCommand->sync()) { return cPhs_INIT_e; } + IF_DUSK_BLOCK_END #endif int rt; @@ -1237,7 +1253,113 @@ void dScnLogo_c::logoInitGC() { ResTIMG* dolbyImg = (ResTIMG*)dComIfG_getObjectRes(LOGO_ARC, 3); mDolbyLogo = JKR_NEW dDlst_2D_c(dolbyImg, 189, 150, 232, 112, 255); -#if VERSION == VERSION_GCN_PAL +#if TARGET_PC + if (getGameVersion() == GameVersion::GcnPal) { + u8 language = getPalLanguage(); + if (language >= 5) { + language = 0; + } + + static const char* choice[] = { + "50_60_choice_eng.bti", + "50_60_choice_ger.bti", + "50_60_choice_fra.bti", + "50_60_choice_spa.bti", + "50_60_choice_ita.bti", + }; + + static const char* yes[] = { + "60_set_eng.bti", + "60_set_ger.bti", + "60_set_fra.bti", + "60_set_spa.bti", + "60_set_ita.bti", + }; + + static const char* no[] = { + "50_set_eng.bti", + "50_set_ger.bti", + "50_set_fra.bti", + "50_set_spa.bti", + "50_set_ita.bti", + }; + + static const char* prog[] = { + "progressive_pro.bti", + "progressive_pro_gm.bti", + "progressive_pro_fr.bti", + "progressive_pro_sp.bti", + "progressive_pro_it.bti", + }; + + static const char* intr[] = { + "progressive_inter.bti", + "progressive_inter_gm.bti", + "progressive_inter_fr.bti", + "progressive_inter_sp.bti", + "progressive_inter_it.bti", + }; + + static const char* warning[] = { + "warning.bti", + "warning_gm.bti", + "warning_fr.bti", + "warning_sp.bti", + "warning_it.bti", + }; + + static const char* warningPs[] = { + "warning_pstart.bti", + "warning_pstart_gm.bti", + "warning_pstart_fr.bti", + "warning_pstart_sp.bti", + "warning_pstart_it.bti", + }; + + ResTIMG* warningImg = (ResTIMG*)mpPalLogoResCommand->getArchive()->getResource('DAT ', warning[language]); + mWarning = JKR_NEW dDlst_2D_c(warningImg, 0, 0, FB_WIDTH, FB_HEIGHT, 255); + + ResTIMG* warnStartImg = (ResTIMG*)mpPalLogoResCommand->getArchive()->getResource('DAT ', warningPs[language]); + mWarningStart = JKR_NEW dDlst_2D_c(warnStartImg, 0, 359, FB_WIDTH, 48, 255); + + ResTIMG* progChoiceImg = (ResTIMG*)mpPalLogoResCommand->getArchive()->getResource('DAT ', choice[language]); + mProgressiveChoice = JKR_NEW dDlst_2D_c(progChoiceImg, 113, 143, 416, 210, 255); + + ResTIMG* progYesImg = (ResTIMG*)mpPalLogoResCommand->getArchive()->getResource('DAT ', yes[language]); + mProgressiveYes = JKR_NEW dDlst_2D_c(progYesImg, 121, 352, 200, 72, 255); + mProgressiveYes->getPicture()->setWhite(JUtility::TColor(160, 160, 160, 255)); + + ResTIMG* progNoImg = (ResTIMG*)mpPalLogoResCommand->getArchive()->getResource('DAT ', no[language]); + mProgressiveNo = JKR_NEW dDlst_2D_c(progNoImg, 320, 352, 200, 72, 255); + mProgressiveNo->getPicture()->setWhite(JUtility::TColor(160, 160, 160, 255)); + + mProgressivePro = (ResTIMG*)mpPalLogoResCommand->getArchive()->getResource('DAT ', prog[language]); + mProgressiveInter = (ResTIMG*)mpPalLogoResCommand->getArchive()->getResource('DAT ', intr[language]); + mProgressiveSel = JKR_NEW dDlst_2D_c(mProgressivePro, 153, 309, 336, 88, 255); + } else { + ResTIMG* warningImg = (ResTIMG*)dComIfG_getObjectRes(LOGO_ARC, 10); + mWarning = JKR_NEW dDlst_2D_c(warningImg, 0, 0, FB_WIDTH, FB_HEIGHT, 255); + + ResTIMG* warnStartImg = (ResTIMG*)dComIfG_getObjectRes(LOGO_ARC, 11); + mWarningStart = JKR_NEW dDlst_2D_c(warnStartImg, 0, 359, FB_WIDTH, 48, 255); + + ResTIMG* progChoiceImg = (ResTIMG*)dComIfG_getObjectRes(LOGO_ARC, 5); + mProgressiveChoice = JKR_NEW dDlst_2D_c(progChoiceImg, 113, 281, 416, 72, 255); + + ResTIMG* progYesImg = (ResTIMG*)dComIfG_getObjectRes(LOGO_ARC, 9); + mProgressiveYes = JKR_NEW dDlst_2D_c(progYesImg, 211, 372, 80, 32, 255); + mProgressiveYes->getPicture()->setWhite(JUtility::TColor(160, 160, 160, 255)); + + ResTIMG* progNoImg = (ResTIMG*)dComIfG_getObjectRes(LOGO_ARC, 7); + mProgressiveNo = JKR_NEW dDlst_2D_c(progNoImg, 350, 372, 80, 32, 255); + mProgressiveNo->getPicture()->setWhite(JUtility::TColor(160, 160, 160, 255)); + + mProgressivePro = (ResTIMG*)dComIfG_getObjectRes(LOGO_ARC, 8); + mProgressiveInter = (ResTIMG*)dComIfG_getObjectRes(LOGO_ARC, 6); + mProgressiveSel = JKR_NEW dDlst_2D_c(mProgressivePro, 153, 309, 336, 88, 255); + } + +#elif VERSION == VERSION_GCN_PAL u8 language = getPalLanguage(); if (language >= 5) { language = 0; @@ -1395,7 +1517,30 @@ void dScnLogo_c::dvdDataLoad() { mpButtonCommand = aramMount(BUTTON_RES_PATH, mDoExt_getJ2dHeap()); mpCardIconCommand = aramMount(ICON_RES_PATH, mDoExt_getJ2dHeap()); - #if VERSION == VERSION_GCN_PAL + #if TARGET_PC + if (getGameVersion() == GameVersion::GcnPal) { + switch (getPalLanguage()) { + case 1: + mpBmgResCommand = onMemMount("/res/Msgde/bmgres.arc"); + break; + case 2: + mpBmgResCommand = onMemMount("/res/Msgfr/bmgres.arc"); + break; + case 3: + mpBmgResCommand = onMemMount("/res/Msgsp/bmgres.arc"); + break; + case 4: + mpBmgResCommand = onMemMount("/res/Msgit/bmgres.arc"); + break; + case 0: + default: + mpBmgResCommand = onMemMount("/res/Msguk/bmgres.arc"); + break; + } + } else { + mpBmgResCommand = onMemMount(MSG_PATH); + } + #elif VERSION == VERSION_GCN_PAL switch (getPalLanguage()) { case 1: mpBmgResCommand = onMemMount("/res/Msgde/bmgres.arc"); @@ -1435,7 +1580,10 @@ void dScnLogo_c::dvdDataLoad() { mpMsgResCommand[1] = aramMount(MSG_RES1_PATH, mDoExt_getJ2dHeap()); mpMsgResCommand[2] = aramMount(MSG_RES2_PATH, mDoExt_getJ2dHeap()); mpMsgResCommand[3] = aramMount(MSG_RES3_PATH, mDoExt_getJ2dHeap()); -#if VERSION == VERSION_GCN_JPN +#if TARGET_PC + const auto res4Path = versionSelect({{GameVersion::GcnJpn, "/res/Layout/msgres04.arc"}}, "/res/Layout/msgres04F.arc"); + mpMsgResCommand[4] = aramMount( res4Path, mDoExt_getJ2dHeap()); +#elif VERSION == VERSION_GCN_JPN mpMsgResCommand[4] = aramMount("/res/Layout/msgres04.arc", mDoExt_getJ2dHeap()); #else mpMsgResCommand[4] = aramMount("/res/Layout/msgres04F.arc", mDoExt_getJ2dHeap()); @@ -1445,7 +1593,23 @@ void dScnLogo_c::dvdDataLoad() { mpMain2DCommand = onMemMount(MAIN2D_PATH); -#if VERSION == VERSION_GCN_JPN +#if TARGET_PC + const auto fontResPath = versionSelect( + { + {GameVersion::GcnJpn, "/res/Fontjp/fontres.arc"}, + {GameVersion::GcnPal, "/res/Fonteu/fontres.arc"}, + }, "/res/Fontus/fontres.arc"); + const auto fontRubyPath = versionSelect( + { + {GameVersion::GcnJpn, "/res/Fontjp/rubyres.arc"}, + {GameVersion::GcnPal, "/res/Fonteu/rubyres.arc"}, + }, "/res/Fontus/rubyres.arc"); + + // Note: GCN_JPN mounts this archive as tail instead of head. + // I'm guessing this is fine since we have more RAM. + mpFontResCommand = onMemMount(fontResPath); + mpRubyResCommand = onMemMount(fontRubyPath); +#elif VERSION == VERSION_GCN_JPN mpFontResCommand = mDoDvdThd_mountXArchive_c::create("/res/Fontjp/fontres.arc", 1, JKRArchive::MOUNT_MEM, NULL); mpRubyResCommand = onMemMount("/res/Fontjp/rubyres.arc"); #elif VERSION == VERSION_GCN_PAL @@ -1545,7 +1709,7 @@ static int dScnLogo_IsDelete(dScnLogo_c* i_this) { return 1; } -#if VERSION == VERSION_GCN_PAL || PLATFORM_WII || PLATFORM_SHIELD +#if TARGET_PC || VERSION == VERSION_GCN_PAL || PLATFORM_WII || PLATFORM_SHIELD u8 dScnLogo_c::getPalLanguage() { u8 language; diff --git a/src/d/d_s_name.cpp b/src/d/d_s_name.cpp index 735ce4d0ca..6baa07da30 100644 --- a/src/d/d_s_name.cpp +++ b/src/d/d_s_name.cpp @@ -5,19 +5,20 @@ #include "d/dolzel.h" // IWYU pragma: keep -#include "d/d_s_name.h" #include "JSystem/JKernel/JKRExpHeap.h" #include "d/d_com_inf_game.h" #include "d/d_meter2_info.h" +#include "d/d_s_name.h" +#include "dusk/imgui/ImGuiConsole.hpp" +#include "dusk/memory.h" +#include "dusk/settings.h" +#include "f_op/f_op_overlap_mng.h" #include "f_op/f_op_scene_mng.h" #include "m_Do/m_Do_Reset.h" #include "m_Do/m_Do_graphic.h" #include "m_Do/m_Do_machine.h" -#include "m_Do/m_Do_mtx.h" #include "m_Do/m_Do_main.h" -#include "f_op/f_op_overlap_mng.h" -#include "dusk/memory.h" -#include "dusk/settings.h" +#include "m_Do/m_Do_mtx.h" #if TARGET_PC #define SHOW_TV_SETTINGS_SCREEN (this->mShowTvSettingsScreen) @@ -412,6 +413,16 @@ void dScnName_c::changeGameScene() { dKy_clear_game_init(); dComIfGs_resetDan(); dComIfGs_setRestartRoomParam(0); + +#if TARGET_PC + if (dusk::getSettings().game.speedrunMode && dusk::getSettings().game.hideTvSettingsScreen) { + // start a new run on file load if a run isn't already in progress + if (!dusk::m_speedrunInfo.m_isRunStarted) { + dusk::ImGuiMenuGame::resetForSpeedrunMode(); + dusk::m_speedrunInfo.startRun(); + } + } +#endif } } 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_save.cpp b/src/d/d_save.cpp index 3289a49c64..be95d1f190 100644 --- a/src/d/d_save.cpp +++ b/src/d/d_save.cpp @@ -16,6 +16,8 @@ #include #include +#include "dusk/version.hpp" + #if PLATFORM_WII || PLATFORM_SHIELD #include #include @@ -1027,7 +1029,7 @@ void dSv_player_config_c::init() { mAttentionType = 0; mVibration = 1; -#if DEBUG +#if DEBUG // DUSK VERSION SUPPORT: This field isn't used, so we can ignore it. mLanguage = SCGetLanguage(); #elif REGION_PAL || VERSION >= VERSION_WII_USA_R2 mLanguage = OSGetLanguage(); @@ -1072,7 +1074,8 @@ void dSv_player_config_c::setVibration(u8 i_status) { } u8 dSv_player_config_c::getPalLanguage() const { -#if VERSION == VERSION_GCN_PAL +#if TARGET_PC || VERSION == VERSION_GCN_PAL + IF_DUSK_BLOCK(dusk::version::getGameVersion() == dusk::version::GameVersion::GcnPal) switch (OSGetLanguage()) { case 0: return LANGUAGE_ENGLISH; @@ -1085,6 +1088,7 @@ u8 dSv_player_config_c::getPalLanguage() const { case 4: return LANGUAGE_ITALIAN; } + IF_DUSK_BLOCK_END #elif VERSION >= VERSION_WII_USA_R0 switch (SCGetLanguage()) { case 1: 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/achievements.cpp b/src/dusk/achievements.cpp new file mode 100644 index 0000000000..071332a7c2 --- /dev/null +++ b/src/dusk/achievements.cpp @@ -0,0 +1,467 @@ +#include "dusk/achievements.h" +#include "dusk/io.hpp" +#include "dusk/main.h" +#include "d/d_com_inf_game.h" +#include "d/d_meter2_info.h" +#include "d/actor/d_a_alink.h" +#include "d/actor/d_a_npc4.h" +#include "d/actor/d_a_player.h" +#include "d/d_demo.h" +#include "f_pc/f_pc_name.h" + +#include +#include + +namespace dusk { + +using json = nlohmann::json; + +static void checkGoatHerding(Achievement& a, int32_t threshMs) { + if (dMeter2Info_getMaxCount() != 20 || dMeter2Info_getNowCount() != 20) { + return; + } + const int32_t elapsed = dMeter2Info_getTimeMs(); + if (elapsed > 0 && elapsed <= threshMs) { + a.progress = 1; + } +} + +static constexpr auto ACHIEVEMENTS_FILENAME = "achievements.json"; + +std::vector AchievementSystem::makeEntries() { + return { + { + { + "hero_of_twilight", + "Hero of Twilight", + "Deliver the finishing blow to Ganondorf.", + AchievementCategory::Story, + false, 0, 0, false + }, + [](Achievement& a, json&) { + const auto* link = static_cast(daPy_getPlayerActorClass()); + if (link != nullptr && link->mProcID == daAlink_c::PROC_GANON_FINISH) { + a.progress = 1; + } + }, + {} + }, + { + { + "rollgoal_8", + "Rollgoal Novice", + "Complete the first 8 rollgoal stages.", + AchievementCategory::Minigame, + true, 8, 0, false + }, + [](Achievement& a, json&) { + a.progress = std::min((int)dComIfGs_getEventReg(0xf63f), 8); + }, + {} + }, + { + { + "rollgoal_all", + "Lost Your Marbles", + "Complete all rollgoal stages.", + AchievementCategory::Minigame, + true, 64, 0, false + }, + [](Achievement& a, json&) { + if (dComIfGs_isEventBit(dSv_event_flag_c::KORO2_ALLCLEAR)) { + a.progress = 64; + } else { + a.progress = dComIfGs_getEventReg(0xf63f); + } + }, + {} + }, + { + { + "goat_30s", + "Ranch Hand", + "Herd all 20 goats into the pen in under 30 seconds.", + AchievementCategory::Minigame, + false, 0, 0, false + }, + [](Achievement& a, json&) { + checkGoatHerding(a, 30000); + }, + {} + }, + { + { + "goat_20s", + "Bane of Howard", + "Herd all 20 goats into the pen in under 20 seconds.", + AchievementCategory::Minigame, + false, 0, 0, false + }, + [](Achievement& a, json&) { + checkGoatHerding(a, 20000); + }, + {} + }, + { + { + "goat_18s", + "King of the Ranch", + "Herd all 20 goats into the pen in under 18 seconds.", + AchievementCategory::Minigame, + false, 0, 0, false + }, + [](Achievement& a, json&) { + checkGoatHerding(a, 18000); + }, + {} + }, + { + { + "cave_of_ordeals", + "Conqueror of Ordeals", + "Clear all 50 floors of the Cave of Ordeals.", + AchievementCategory::Challenge, + false, 0, 0, false + }, + [](Achievement& a, json&) { + if (daNpcF_chkEvtBit(0x1F9)) { + a.progress = 1; + } + }, + {} + }, + { + { + "cave_of_ordeals_heartless", + "Indomitable", + "Clear all 50 floors of the Cave of Ordeals with only 3 heart containers.", + AchievementCategory::Challenge, + false, 0, 0, false + }, + [](Achievement& a, json&) { + if (daNpcF_chkEvtBit(0x1F9) && dComIfGs_getMaxLife() <= 15) { + a.progress = 1; + } + }, + {} + }, + { + { + "speedrun_12h", + "Been There Done That", + "Defeat Ganondorf with a total save file play time under 12 hours.", + AchievementCategory::Challenge, + false, 0, 0, false + }, + [](Achievement& a, json&) { + const auto* link = static_cast(daPy_getPlayerActorClass()); + if (link == nullptr || link->mProcID != daAlink_c::PROC_GANON_FINISH) { + return; + } + const int64_t ticks = (static_cast(OSGetTime()) - dComIfGs_getSaveStartTime()) + dComIfGs_getSaveTotalTime(); + if (ticks / OS_TIMER_CLOCK < 12 * 3600) { + a.progress = 1; + } + }, + {} + }, + { + { + "speedrun_8h", + "Swift Blade", + "Defeat Ganondorf with a total save file play time under 6 hours.", + AchievementCategory::Challenge, + false, 0, 0, false + }, + [](Achievement& a, json&) { + const auto* link = static_cast(daPy_getPlayerActorClass()); + if (link == nullptr || link->mProcID != daAlink_c::PROC_GANON_FINISH) { + return; + } + const int64_t ticks = (static_cast(OSGetTime()) - dComIfGs_getSaveStartTime()) + dComIfGs_getSaveTotalTime(); + if (ticks / OS_TIMER_CLOCK < 8 * 3600) { + a.progress = 1; + } + }, + {} + }, + { + { + "princess_of_bugs", + "The Princess of Bugs", + "Deliver all 24 golden bugs to Agitha.", + AchievementCategory::Collection, + true, 24, 0, false + }, + [](Achievement& a, json&) { + a.progress = dComIfGs_checkGetInsectNum(); + }, + {} + }, + { + { + "all_poes", + "Poe Collector", + "Collect all 60 Poe Souls.", + AchievementCategory::Collection, + true, 60, 0, false + }, + [](Achievement& a, json&) { + a.progress = dComIfGs_getPohSpiritNum(); + }, + {} + }, + { + { + "hylian_loach", + "Legendary Catch", + "Catch a Hylian Loach.", + AchievementCategory::Collection, + false, 0, 0, false + }, + [](Achievement& a, json&) { + if (dComIfGs_getFishNum(1) > 0) { + a.progress = 1; + } + }, + {} + }, + { + { + "all_fish", + "Gone Fishin'", + "Catch all 6 species of fish.", + AchievementCategory::Collection, + true, 6, 0, false + }, + [](Achievement& a, json&) { + int nUniqueFish = 0; + for (int i = 0; i < 6; ++i) { + if (dComIfGs_getFishNum(i) != 0) { + nUniqueFish++; + } + } + a.progress = nUniqueFish; + }, + {} + }, + { + { + "a_big_heart", + "A Big Heart", + "Reach maximum health with all 20 heart containers.", + AchievementCategory::Collection, + true, 20, 0, false + }, + [](Achievement& a, json&) { + a.progress = dComIfGs_getMaxLife() / 5; + }, + {} + }, + { + { + "back_in_time", + "Back in Time", + "Perform the Back in Time glitch to play on the title screen.", + AchievementCategory::Glitched, + false, 0, 0, false + }, + [](Achievement& a, json&) { + static int titleNoDemoFrames = 0; + if (fopAcM_SearchByName(fpcNm_TITLE_e) == nullptr) { + titleNoDemoFrames = 0; + return; + } + const auto* link = static_cast(daPy_getPlayerActorClass()); + if (link != nullptr && dDemo_c::getMode() == 0) { + if (++titleNoDemoFrames >= 60) { + a.progress = 1; + } + } else { + titleNoDemoFrames = 0; + } + }, + {} + }, + { + { + "early_master_sword", + "Early Master Sword", + "Obtain the Master Sword before completing Midna's Desperate Hour.", + AchievementCategory::Glitched, + false, 0, 0, false + }, + [](Achievement& a, json&) { + if (dComIfGs_isCollectSword(COLLECT_MASTER_SWORD) && !dComIfGs_isEventBit(0x1E08)) { + a.progress = 1; + } + }, + {} + }, + { + { + "earliest_master_sword", + "Earliest Master Sword", + "Obtain the Master Sword before meeting Midna.", + AchievementCategory::Glitched, + false, 0, 0, false + }, + [](Achievement& a, json&) { + if (dComIfGs_isCollectSword(COLLECT_MASTER_SWORD) && !dComIfGs_isTransformLV(0)) { + a.progress = 1; + } + }, + {} + }, + { + { + "ultimate_delivery", + "The Ultimate Delivery", + "Have all 16 postman letters at the same time.", + AchievementCategory::Glitched, + true, 16, 0, false + }, + [](Achievement& a, json&) { + a.progress = dMeter2Info_getRecieveLetterNum(); + }, + {} + }, + { + { + "speedrun_4h", + "Hero of Time", + "Defeat Ganondorf with a total save file play time under 4 hours.", + AchievementCategory::Glitched, + false, 0, 0, false + }, + [](Achievement& a, json&) { + const auto* link = static_cast(daPy_getPlayerActorClass()); + if (link == nullptr || link->mProcID != daAlink_c::PROC_GANON_FINISH) { + return; + } + const int64_t ticks = (static_cast(OSGetTime()) - dComIfGs_getSaveStartTime()) + dComIfGs_getSaveTotalTime(); + if (ticks / OS_TIMER_CLOCK < 4 * 3600) { + a.progress = 1; + } + }, + {} + } + }; +} + +AchievementSystem::AchievementSystem() : m_entries(makeEntries()) {} + +AchievementSystem& AchievementSystem::get() { + static AchievementSystem instance; + return instance; +} + +std::string AchievementSystem::consumePendingUnlock() { + std::string msg = std::move(m_pendingUnlocks.front()); + m_pendingUnlocks.pop(); + return msg; +} + +std::vector AchievementSystem::getAchievements() const { + std::vector result; + result.reserve(m_entries.size()); + for (const auto& e : m_entries) { + result.push_back(e.achievement); + } + return result; +} + +void AchievementSystem::load() { + m_loaded = true; + const auto filePath = dusk::ConfigPath / ACHIEVEMENTS_FILENAME; + if (!std::filesystem::exists(filePath)) { + return; + } + try { + auto data = io::FileStream::ReadAllBytes(filePath.string().c_str()); + auto j = json::parse(data); + if (!j.is_object()) { + return; + } + for (auto& e : m_entries) { + if (!j.contains(e.achievement.key)) { + continue; + } + const auto& entry = j[e.achievement.key]; + if (entry.contains("progress")) { + e.achievement.progress = entry["progress"].get(); + } + if (entry.contains("unlocked")) { + e.achievement.unlocked = entry["unlocked"].get(); + } + if (entry.contains("extra")) { + e.extra = entry["extra"]; + } + } + } catch (const std::exception&) {} +} + +void AchievementSystem::save() { + json j = json::object(); + for (const auto& e : m_entries) { + json entry = { + {"progress", e.achievement.progress}, + {"unlocked", e.achievement.unlocked}, + }; + if (!e.extra.is_null()) { + entry["extra"] = e.extra; + } + j[e.achievement.key] = std::move(entry); + } + try { + io::FileStream::WriteAllText( + (dusk::ConfigPath / ACHIEVEMENTS_FILENAME).string().c_str(), + j.dump(2) + ); + } catch (const std::exception&) {} +} + +void AchievementSystem::clearAll() { + m_entries = makeEntries(); + save(); +} + +void AchievementSystem::processEntry(Entry& e) { + if (e.achievement.unlocked) { + return; + } + const int32_t prevProgress = e.achievement.progress; + e.check(e.achievement, e.extra); + + const bool progressChanged = e.achievement.progress != prevProgress; + const bool nowUnlocked = e.achievement.isCounter ? + e.achievement.progress >= e.achievement.goal : + e.achievement.progress > 0; + + if (nowUnlocked) { + e.achievement.progress = e.achievement.isCounter ? e.achievement.goal : 1; + e.achievement.unlocked = true; + m_pendingUnlocks.push(e.achievement.name); + m_dirty = true; + } else if (progressChanged) { + m_dirty = true; + } +} + +void AchievementSystem::tick() { + if (!m_loaded) { + load(); + } + if (!dusk::IsGameLaunched) { + return; + } + for (auto& e : m_entries) { + processEntry(e); + } + if (m_dirty) { + save(); + m_dirty = false; + } +} + +} // namespace dusk 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..856cdce4a3 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) { @@ -154,6 +154,7 @@ namespace dusk::config { template class ConfigImpl; template class ConfigImpl; template class ConfigImpl; + template class ConfigImpl; } void dusk::config::Register(ConfigVarBase& configVar) { diff --git a/src/dusk/crash_reporting.cpp b/src/dusk/crash_reporting.cpp index 73f432e418..657e5ef5d2 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() { @@ -166,7 +167,6 @@ void InitializeCrashReporting() { sentry_set_tag("git_branch", DUSK_WC_BRANCH); sentry_set_tag("build_type", DUSK_BUILD_TYPE); - sentry_set_tag("tp_version", DUSK_TP_VERSION); g_sentryInitialized = true; DuskLog.info("Initialized Sentry crash reporting"); 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/dvd_asset.cpp b/src/dusk/dvd_asset.cpp index b9e0729be9..75c82107cc 100644 --- a/src/dusk/dvd_asset.cpp +++ b/src/dusk/dvd_asset.cpp @@ -80,8 +80,19 @@ static s32 DolVaToFileOffset(u32 va) { return -1; } -bool LoadDolAsset(void* dst, u32 virtualAddress, s32 size) { - s32 fileOffset = DolVaToFileOffset(virtualAddress); +static u32 GetOffsetForVersion(std::initializer_list version) { + const auto gameVersion = dusk::version::getGameVersion(); + for (auto elem : version) { + if (elem.mGameVersion == gameVersion) { + return elem.mOffset; + } + } + + DuskLog.fatal("Unable to find offset for this game version!"); +} + +bool LoadDolAsset(void* dst, std::initializer_list virtualAddress, s32 size) { + s32 fileOffset = DolVaToFileOffset(GetOffsetForVersion(virtualAddress)); if (fileOffset < 0) { return false; } @@ -95,13 +106,16 @@ bool LoadDolAsset(void* dst, u32 virtualAddress, s32 size) { return true; } -bool LoadRelAsset(void* dst, const char* dvdPath, s32 offset, s32 size) { - void* p = JKRDvdRipper::loadToMainRAM(dvdPath, (u8*)dst, EXPAND_SWITCH_UNKNOWN1, (u32)size, nullptr, JKRDvdRipper::ALLOC_DIRECTION_FORWARD, (u32)offset, nullptr, nullptr); - if (!p) DuskLog.fatal("dvd_asset: failed to load {} (offset={:#x} size={:#x})", dvdPath, offset, size); +bool LoadRelAsset(void* dst, const char* dvdPath, std::initializer_list offset, s32 size) { + auto resOffset = GetOffsetForVersion(offset); + void* p = JKRDvdRipper::loadToMainRAM(dvdPath, (u8*)dst, EXPAND_SWITCH_UNKNOWN1, (u32)size, nullptr, JKRDvdRipper::ALLOC_DIRECTION_FORWARD, resOffset, nullptr, nullptr); + if (!p) DuskLog.fatal("dvd_asset: failed to load {} (offset={:#x} size={:#x})", dvdPath, resOffset, size); return p != nullptr; } -bool LoadArchivedRelAsset(void* dst, u32 memType, const char* relFileName, s32 offset, s32 size) { +bool LoadArchivedRelAsset(void* dst, u32 memType, const char* relFileName, std::initializer_list offset, s32 size) { + auto resOffset = GetOffsetForVersion(offset); + // On TARGET_PC, cDyl_InitCallback skips DynamicModuleControl::initialize() due to static linking // Mount RELS.arc on first use so sArchive is available static bool s_mountAttempted = false; @@ -118,7 +132,7 @@ bool LoadArchivedRelAsset(void* dst, u32 memType, const char* relFileName, s32 o DuskLog.fatal("dvd_asset: {} not found in RELS archive", relFileName); return false; } - std::memcpy(dst, rel + offset, size); + std::memcpy(dst, rel + resOffset, size); return true; } diff --git a/src/dusk/extras.c b/src/dusk/extras.c index e693985479..a1fb26e998 100644 --- a/src/dusk/extras.c +++ b/src/dusk/extras.c @@ -3,6 +3,27 @@ #include #include +#ifdef _MSC_VER +#include +#endif + +void __dcbz(void* addr, int offset) { + // Gekko cache lines are 32 bytes. + // dcbz usually requires addr to be 32-byte aligned. + memset((char*)addr + offset, 0, 32); +} + +int __cntlzw(unsigned int val) { + if (val == 0) return 32; // PowerPC returns 32 if the input is 0 +#ifdef _MSC_VER + unsigned long idx; + _BitScanReverse(&idx, val); + return 31 - (int)idx; +#else + return __builtin_clz(val); +#endif +} + #ifndef _MSC_VER int stricmp(const char* str1, const char* str2) { char a_var; @@ -48,11 +69,6 @@ int strnicmp(const char* str1, const char* str2, int n) { } #endif - -void *_memcpy(void* dest, void const* src, int n) { - return memcpy(dest, src, n); -} - void DCZeroRange(void* addr, uint32_t nBytes) { #if defined(_MSC_VER) || TARGET_ANDROID memset(addr, 0, nBytes); diff --git a/src/dusk/extras.cpp b/src/dusk/extras.cpp deleted file mode 100644 index de402f028b..0000000000 --- a/src/dusk/extras.cpp +++ /dev/null @@ -1,27 +0,0 @@ -// C++ Mangled version of extras.c -#include -#include -#ifdef _MSC_VER -#include -#endif - -void *__memcpy(void* dest, void const* src, int n) { - return memcpy(dest, src, n); -} - -void __dcbz(void* addr, int offset) { - // Gekko cache lines are 32 bytes. - // dcbz usually requires addr to be 32-byte aligned. - memset((char*)addr + offset, 0, 32); -} - -int __cntlzw(unsigned int val) { - if (val == 0) return 32; // PowerPC returns 32 if the input is 0 -#ifdef _MSC_VER - unsigned long idx; - _BitScanReverse(&idx, val); - return 31 - (int)idx; -#else - return __builtin_clz(val); -#endif -} 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 e3de48bcfc..941c9ed318 100644 --- a/src/dusk/frame_interpolation.cpp +++ b/src/dusk/frame_interpolation.cpp @@ -1,79 +1,31 @@ #include "dusk/frame_interpolation.h" #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; struct CameraSnapshot { cXyz eye{}; @@ -84,6 +36,7 @@ struct CameraSnapshot { f32 aspect{}; f32 near_{}; f32 far_{}; + bool wideZoom{}; bool valid{}; }; @@ -93,6 +46,13 @@ 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; @@ -105,14 +65,6 @@ void copy_view_to_snap(CameraSnapshot* dst, const view_class& v) { dst->valid = true; } -inline void copy_matrix(const Mtx src, Mtx dst) { - MTXCopy(src, dst); -} - -inline void concat_matrix(const Mtx lhs, const Mtx rhs, Mtx out) { - MTXConcat(lhs, rhs, out); -} - inline void lerp_matrix(Mtx out, const Mtx lhs, const Mtx rhs, float step) { const float old_weight = 1.0f - step; for (size_t row = 0; row < 3; ++row) { @@ -146,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() { @@ -312,10 +124,33 @@ 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(); @@ -324,7 +159,6 @@ void begin_record() { 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; @@ -334,8 +168,6 @@ void begin_record() { 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(); @@ -345,8 +177,6 @@ void begin_record() { s_cam_prev.valid = false; s_cam_curr.valid = false; return; - } else { - copy_view_to_snap(&s_cam_prev, cam->view); } } @@ -354,21 +184,20 @@ 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() { @@ -391,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; } @@ -493,7 +270,7 @@ 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; } @@ -502,6 +279,77 @@ void record_camera(::camera_process_class* cam, int camera_id) { return; } copy_view_to_snap(&s_cam_curr, cam->view); +#if WIDESCREEN_SUPPORT + s_cam_curr.wideZoom = mDoGph_gInf_c::isWideZoom(); +#endif +} + +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(); + const bool is_cam_curr_authoritative = g_is_sim_frame && step <= 0.0f; + + cXyz eye; + cXyz center; + cXyz up; + if (is_cam_curr_authoritative) { + eye = s_cam_curr.eye; + center = s_cam_curr.center; + up = s_cam_curr.up; + } else { + 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_cam_curr.up; + up.normalizeRS(); + } + + view->lookat.eye = eye; + view->lookat.center = center; + view->lookat.up = up; + if (is_cam_curr_authoritative) { + view->bank = s_cam_curr.bank; + view->fovy = s_cam_curr.fovy; + view->aspect = s_cam_curr.aspect; + view->near_ = s_cam_curr.near_; + view->far_ = s_cam_curr.far_; + } else { + 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; + } + + // 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 + const f32 wide_step = is_cam_curr_authoritative ? 1.0f : step; + if (mDoGph_gInf_c::isWide() && !mDoGph_gInf_c::isWideZoom() && wide_step >= 0.5f ? s_cam_curr.wideZoom : s_cam_prev.wideZoom) { + mDoGph_gInf_c::onWideZoom(); + } +#endif +} + +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() { @@ -523,27 +371,7 @@ void begin_presentation_camera() { } std::memcpy(&s_presentation_view_backup, view, sizeof(view_class)); - - const f32 step = get_interpolation_step(); - cXyz eye; - cXyz center; - cXyz up; - 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_cam_curr.up; - up.normalizeRS(); - } - - 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; + 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_); @@ -601,7 +429,11 @@ void begin_presentation_camera() { 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() { @@ -618,20 +450,4 @@ void end_presentation_camera() { std::memcpy(view, &s_presentation_view_backup, sizeof(view_class)); } } - -uint64_t alloc_simple_shadow_pair_base() { - if (!s_initialized || !g_recording || g_current_path.size() <= 1) { - return 0; - } - - 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; - } - 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/gamepad_color.cpp b/src/dusk/gamepad_color.cpp new file mode 100644 index 0000000000..cba32da632 --- /dev/null +++ b/src/dusk/gamepad_color.cpp @@ -0,0 +1,106 @@ +#include +#include +#include +#include +#include +#include + +cXyz currentGamepadColor = {0, 0, 0}; +cXyz finalGamepadColor = {0, 0, 0}; +cXyz additionalGamepadColor = {0, 0, 0}; + +float lerpSpeed = 0.0f; + +const cXyz duskColor = {50, 50, -50}; +const cXyz noColor = {0, 0, 0}; + +cXyz LerpColor(cXyz a, cXyz b, float t) { + return {std::lerp(a.x, b.x, t), std::lerp(a.y, b.y, t), std::lerp(a.z, b.z, t)}; +} + +void FadeLED(cXyz newColor, float speed) { + finalGamepadColor = newColor; + lerpSpeed = speed / 30.0f; +} + +void SetLED(cXyz newColor) { + currentGamepadColor = newColor; + finalGamepadColor = newColor; +} + +void SetGamepadAdditionalColor(cXyz addColor) { + additionalGamepadColor.x = addColor.x; + additionalGamepadColor.y = addColor.y; + additionalGamepadColor.z = addColor.z; +} + +void handleGamepadColor() { + bool setColor = false; + + fopAc_ac_c* zhint = dComIfGp_att_getZHint(); + if (zhint != NULL) { + FadeLED({50, 50, 175}, 2.0f); + setColor = true; + } + + daPy_py_c* player = daPy_getPlayerActorClass(); + daAlink_c* link = daAlink_getAlinkActorClass(); + + if (link != nullptr && !setColor) { + if (link->checkWolf()) { + FadeLED({115, 115, 75}, 5.0f); + setColor = true; + } else { + switch (dComIfGs_getSelectEquipClothes()) { + case dItemNo_WEAR_KOKIRI_e: + FadeLED({0, 100, 0}, 5.0f); + setColor = true; + break; + case dItemNo_WEAR_ZORA_e: + FadeLED({0, 0, 100}, 5.0f); + setColor = true; + break; + case dItemNo_ARMOR_e: + if (link->checkMagicArmorHeavy()) { + FadeLED({5, 100, 100}, 5.0f); + } else { + FadeLED({100, 0, 5}, 5.0f); + } + setColor = true; + break; + case dItemNo_WEAR_CASUAL_e: + FadeLED({235, 230, 115}, 5.0f); + setColor = true; + break; + } + } + } + + if (dKy_darkworld_check()) { + SetGamepadAdditionalColor(duskColor); + } else { + SetGamepadAdditionalColor(noColor); + } + + f32 finalRed = finalGamepadColor.x + additionalGamepadColor.x; + f32 finalGreen = finalGamepadColor.y + additionalGamepadColor.y; + f32 finalBlue = finalGamepadColor.z + additionalGamepadColor.z; + + if (finalRed > 255) + finalRed = 255; + if (finalRed < 0) + finalRed = 0; + + if (finalGreen > 255) + finalGreen = 255; + if (finalGreen < 0) + finalGreen = 0; + + if (finalBlue > 255) + finalBlue = 255; + if (finalBlue < 0) + finalBlue = 0; + + currentGamepadColor = LerpColor(currentGamepadColor, cXyz{finalRed, finalGreen, finalBlue}, lerpSpeed); + PADSetColor(PAD_CHAN0, (u8)currentGamepadColor.x, (u8)currentGamepadColor.y, (u8)currentGamepadColor.z); +} \ No newline at end of file diff --git a/src/dusk/gyro.cpp b/src/dusk/gyro.cpp index a2b026713a..abe22909c1 100644 --- a/src/dusk/gyro.cpp +++ b/src/dusk/gyro.cpp @@ -1,33 +1,47 @@ #include "dusk/gyro.h" #include "d/actor/d_a_alink.h" +#include namespace dusk::gyro { namespace { -// TODO: Make deadband and smoothing configurable -constexpr float kDeadbandRadS = 0.04f; -constexpr float kSmoothAlpha = 0.35f; -constexpr s32 kRollgoalTableMaxOffset = 12000; +constexpr s32 kRollgoalTableMaxOffset = 6500; +constexpr float kGyroEmaAlphaMin = 0.05f; +constexpr float kGyroEmaAlphaMax = 1.0f; +// Smooth gravity separately so the yaw/roll blend doesn't twitch with raw accel noise. +constexpr float kGravityEmaAlpha = 0.1f; +constexpr float kMinGravityProjection = 0.2f; +// Let roll contribute more strongly as the pad approaches an upright posture. +constexpr float kRollAimBoostMax = 2.0f; bool s_sensor_enabled = false; +bool s_accel_enabled = false; +bool s_was_aiming = false; +bool s_have_gravity_baseline = false; float s_smooth_gx = 0.0f; float s_smooth_gy = 0.0f; float s_smooth_gz = 0.0f; +float s_gravity_y = 0.0f; +float s_gravity_z = 0.0f; +float s_baseline_gravity_y = 0.0f; +float s_baseline_gravity_z = 0.0f; float s_yaw_rad = 0.0f; -float s_yaw_rad_pending = 0.0f; float s_pitch_rad = 0.0f; -float s_pitch_rad_pending = 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_pending = s_pitch_rad_pending = s_roll_rad = 0.0f; + s_gravity_y = s_gravity_z = 0.0f; + s_baseline_gravity_y = s_baseline_gravity_z = 0.0f; + s_was_aiming = false; + s_have_gravity_baseline = false; + s_yaw_rad = s_pitch_rad = s_roll_rad = 0.0f; s_rollgoal_ax = s_rollgoal_az = 0; } -float apply_deadband(float v) { - if (v > -kDeadbandRadS && v < kDeadbandRadS) { +float apply_deadband(float v, float deadband_rad_s) { + if (v > -deadband_rad_s && v < deadband_rad_s) { return 0.0f; } return v; @@ -35,11 +49,10 @@ float apply_deadband(float 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 queryGyroAimItemContext() { +bool queryGyroAimContext() { if (!static_cast(dusk::getSettings().game.enableGyroAim)) { return false; } @@ -49,19 +62,34 @@ bool queryGyroAimItemContext() { return false; } - return link->checkGyroAimItemContext() && dComIfGp_checkCameraAttentionStatus(link->field_0x317c, 0x10); + return link->checkGyroAimContext() && dComIfGp_checkCameraAttentionStatus(link->field_0x317c, 0x10); } void read(float dt) { - if (!s_sensor_keep_alive && !(dusk::getSettings().game.enableGyroAim && queryGyroAimItemContext())) { + const bool aim_active = queryGyroAimContext(); + const bool aim_just_started = aim_active && !s_was_aiming; + const bool aim_just_ended = !aim_active && s_was_aiming; + s_was_aiming = aim_active; + + if (!s_sensor_keep_alive && !aim_active) { if (s_sensor_enabled) { PADSetSensorEnabled(PAD_CHAN0, PAD_SENSOR_GYRO, FALSE); s_sensor_enabled = false; } + if (s_accel_enabled) { + PADSetSensorEnabled(PAD_CHAN0, PAD_SENSOR_ACCEL, FALSE); + s_accel_enabled = false; + } reset_filter_state(); return; } + if (aim_just_started || aim_just_ended) { + s_gravity_y = s_gravity_z = 0.0f; + s_baseline_gravity_y = s_baseline_gravity_z = 0.0f; + s_have_gravity_baseline = false; + } + if (!s_sensor_enabled) { if (!PADHasSensor(PAD_CHAN0, PAD_SENSOR_GYRO)) { return; @@ -72,27 +100,78 @@ void read(float dt) { s_sensor_enabled = true; } + if (!s_accel_enabled && PADHasSensor(PAD_CHAN0, PAD_SENSOR_ACCEL) && + PADSetSensorEnabled(PAD_CHAN0, PAD_SENSOR_ACCEL, TRUE)) + { + // We only need accel for the gravity-aware yaw/roll mix. + s_accel_enabled = true; + } + f32 gyro[3]; if (!PADGetSensorData(PAD_CHAN0, PAD_SENSOR_GYRO, gyro, 3)) { return; } - s_smooth_gx += kSmoothAlpha * (gyro[0] - s_smooth_gx); - s_smooth_gy += kSmoothAlpha * (gyro[1] - s_smooth_gy); - s_smooth_gz += kSmoothAlpha * (gyro[2] - s_smooth_gz); + const float smooth_alpha = kGyroEmaAlphaMax + dusk::getSettings().game.gyroSmoothing * (kGyroEmaAlphaMin - kGyroEmaAlphaMax); + const float deadband = dusk::getSettings().game.gyroDeadband; - s_pitch_rad = apply_deadband(s_smooth_gx) * dt * dusk::getSettings().game.gyroAimSensitivityX; - s_yaw_rad = apply_deadband(s_smooth_gy) * dt * dusk::getSettings().game.gyroAimSensitivityY; - s_roll_rad = apply_deadband(s_smooth_gz) * dt * dusk::getSettings().game.gyroAimSensitivityX; // GYRO NOTE: Exposing Z sensitivity seems unusual, so I'm just using X + 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_pending += s_pitch_rad; - s_yaw_rad_pending += s_yaw_rad; + const float pitch_rate = apply_deadband(s_smooth_gx, deadband); + const float yaw_rate = apply_deadband(s_smooth_gy, deadband); + const float roll_rate = apply_deadband(s_smooth_gz, deadband); + + s_pitch_rad = -pitch_rate * dt * dusk::getSettings().game.gyroSensitivityX; + s_roll_rad = roll_rate * dt * dusk::getSettings().game.gyroSensitivityX; // GYRO NOTE: Exposing Z sensitivity seems unusual, so I'm just using X + + float horizontal_rate = yaw_rate; + if (aim_active && s_accel_enabled) { + f32 accel[3]; + if (PADGetSensorData(PAD_CHAN0, PAD_SENSOR_ACCEL, accel, 3)) { + if (!s_have_gravity_baseline) { + s_gravity_y = accel[1]; + s_gravity_z = accel[2]; + } else { + s_gravity_y += kGravityEmaAlpha * (accel[1] - s_gravity_y); + s_gravity_z += kGravityEmaAlpha * (accel[2] - s_gravity_z); + } + + // Compare the current gravity projection against the gravity vector from + // aim start so the user's resting hold angle becomes the neutral baseline. + const float gravity_yz_len = std::sqrt((s_gravity_y * s_gravity_y) + (s_gravity_z * s_gravity_z)); + if (gravity_yz_len >= kMinGravityProjection) { + const float current_gravity_y = s_gravity_y / gravity_yz_len; + const float current_gravity_z = s_gravity_z / gravity_yz_len; + + if (!s_have_gravity_baseline) { + s_baseline_gravity_y = current_gravity_y; + s_baseline_gravity_z = current_gravity_z; + s_have_gravity_baseline = true; + } + + const float yaw_weight = + (s_baseline_gravity_y * current_gravity_y) + (s_baseline_gravity_z * current_gravity_z); + const float roll_weight = + (s_baseline_gravity_y * current_gravity_z) - (s_baseline_gravity_z * current_gravity_y); + const float roll_mix = std::fabs(roll_weight); + const float roll_boost = 1.0f + (roll_mix * (kRollAimBoostMax - 1.0f)); + horizontal_rate = (yaw_rate * yaw_weight) + (roll_rate * roll_weight * roll_boost); + } + } + } + + s_yaw_rad = horizontal_rate * dt * dusk::getSettings().game.gyroSensitivityY; + + 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 consumeAimDeltas(float& out_yaw_rad, float& out_pitch_rad) { - out_yaw_rad = s_yaw_rad_pending; - out_pitch_rad = s_pitch_rad_pending; - s_yaw_rad_pending = s_pitch_rad_pending = 0.0f; +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) { @@ -101,13 +180,14 @@ void rollgoalTick(bool play_active, s16 camera_yaw) { return; } - const float pitch_rad = s_pitch_rad * dusk::getSettings().game.gyroRollgoalSensitivity; - const float roll_rad = s_roll_rad * dusk::getSettings().game.gyroRollgoalSensitivity; + 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, static_cast(-camera_yaw)); + cMtx_YrotS(*calc_mtx, -camera_yaw); MtxPosition(&in, &out); s_rollgoal_ax += cM_rad2s(out.z); @@ -116,8 +196,8 @@ void rollgoalTick(bool play_active, s16 camera_yaw) { s_rollgoal_az = std::clamp(s_rollgoal_az, -kRollgoalTableMaxOffset, kRollgoalTableMaxOffset); } -void rollgoalTableOffset(s16& out_add_x, s16& out_add_z) { - out_add_x = static_cast(s_rollgoal_ax); - out_add_z = static_cast(s_rollgoal_az); +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/imgui/ImGuiAchievements.cpp b/src/dusk/imgui/ImGuiAchievements.cpp new file mode 100644 index 0000000000..4b844b8386 --- /dev/null +++ b/src/dusk/imgui/ImGuiAchievements.cpp @@ -0,0 +1,228 @@ +#include "ImGuiAchievements.hpp" +#include "ImGuiConfig.hpp" +#include "dusk/achievements.h" +#include "dusk/settings.h" +#include "fmt/format.h" +#include "imgui.h" + +namespace dusk { + +void ImGuiAchievements::notify(std::string name) { + if (m_notifyTimer <= 0.f) { + m_notifyName = std::move(name); + m_notifyTimer = NOTIFY_DURATION; + } else { + m_notifyQueue.push(std::move(name)); + } +} + +void ImGuiAchievements::showNotification() { + if (!getSettings().game.enableAchievementNotifications.getValue()) { + return; + } + if (m_notifyTimer <= 0.f) { + if (m_notifyQueue.empty()) { + return; + } + m_notifyName = std::move(m_notifyQueue.front()); + m_notifyQueue.pop(); + m_notifyTimer = NOTIFY_DURATION; + } + + m_notifyTimer -= ImGui::GetIO().DeltaTime; + + const float alpha = std::min({ + m_notifyTimer / NOTIFY_FADE_TIME, + (NOTIFY_DURATION - m_notifyTimer) / NOTIFY_FADE_TIME, + 1.0f + }); + + const ImGuiViewport* viewport = ImGui::GetMainViewport(); + const float padding = 12.0f; + ImGui::SetNextWindowPos( + ImVec2(viewport->WorkPos.x + viewport->WorkSize.x - padding, viewport->WorkPos.y + padding), + ImGuiCond_Always, ImVec2(1.0f, 0.0f) + ); + + ImGui::SetNextWindowBgAlpha(alpha * 0.92f); + ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(0.08f, 0.06f, 0.01f, alpha * 0.92f)); + ImGui::PushStyleColor(ImGuiCol_Border, ImVec4(1.0f, 0.8f, 0.1f, alpha)); + ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 1.0f, 1.0f, alpha)); + ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 2.0f); + ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(14.0f, 10.0f)); + + constexpr ImGuiWindowFlags flags = + ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_AlwaysAutoResize | + ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoFocusOnAppearing | + ImGuiWindowFlags_NoNav | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoInputs; + + if (ImGui::Begin("##achievement_notify", nullptr, flags)) { + ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 0.82f, 0.1f, alpha)); + ImGui::TextUnformatted("Achievement Unlocked!"); + ImGui::PopStyleColor(); + ImGui::Spacing(); + ImGui::TextUnformatted(m_notifyName.c_str()); + } + ImGui::End(); + + ImGui::PopStyleVar(2); + ImGui::PopStyleColor(3); +} + +void ImGuiAchievements::draw(bool& open) { + showNotification(); + + if (!open) { + return; + } + + ImGui::SetNextWindowSizeConstraints(ImVec2(640, 200), ImVec2(800, 900)); + ImGui::SetNextWindowSize(ImVec2(640, 480), ImGuiCond_FirstUseEver); + + if (!ImGui::Begin( + "Achievements", &open, + ImGuiWindowFlags_NoFocusOnAppearing | ImGuiWindowFlags_NoNav) + ) + { + ImGui::End(); + return; + } + + const auto achievements = AchievementSystem::get().getAchievements(); + + int unlocked = 0; + for (const auto& a : achievements) { + if (a.unlocked) { + ++unlocked; + } + } + + ImGui::Text("%d / %d achievements unlocked", unlocked, (int)achievements.size()); + ImGui::SameLine(); + config::ImGuiCheckbox("Notifications", getSettings().game.enableAchievementNotifications); + ImGui::Separator(); + + static const struct { + AchievementCategory cat; + const char* label; + ImVec4 color; + } ACHIEVEMENT_CATEGORIES[] = { + {AchievementCategory::Story, "Story", ImVec4(1.0f, 0.82f, 0.1f, 1.0f)}, + {AchievementCategory::Collection, "Collection", ImVec4(0.3f, 0.85f, 0.4f, 1.0f)}, + {AchievementCategory::Challenge, "Challenge", ImVec4(1.0f, 0.65f, 0.15f, 1.0f)}, + {AchievementCategory::Minigame, "Minigame", ImVec4(0.5f, 0.85f, 1.0f, 1.0f)}, + {AchievementCategory::Glitched, "Glitched", ImVec4(0.75f, 0.4f, 1.0f, 1.0f)}, + }; + + const float footerHeight = ImGui::GetStyle().ItemSpacing.y + ImGui::GetFrameHeightWithSpacing(); + + if (ImGui::BeginTabBar("##achievement_tabs", ImGuiTabBarFlags_FittingPolicyScroll)) { + for (const auto& catInfo : ACHIEVEMENT_CATEGORIES) { + int catTotal = 0, catUnlocked = 0; + for (const auto& a : achievements) { + if (a.category == catInfo.cat) { + ++catTotal; + if (a.unlocked) { + ++catUnlocked; + } + } + } + if (catTotal == 0) { + continue; + } + + const std::string tabLabel = fmt::format("{} ({}/{})", catInfo.label, catUnlocked, catTotal); + + ImGui::PushStyleColor(ImGuiCol_Text, catInfo.color); + const bool tabOpen = ImGui::BeginTabItem(tabLabel.c_str()); + ImGui::PopStyleColor(); + + if (tabOpen) { + ImGui::BeginChild( + "##cat_list", + ImVec2(0, -footerHeight), + ImGuiChildFlags_None, + ImGuiWindowFlags_NoBackground + ); + + ImGui::Spacing(); + + for (const auto& a : achievements) { + if (a.category != catInfo.cat) { + continue; + } + ImGui::PushID(a.key); + + ImGui::PushStyleColor( + ImGuiCol_Text, + a.unlocked ? + ImVec4(1.0f, 0.65f, 0.15f, 1.0f) : + ImGui::GetStyleColorVec4(ImGuiCol_Text) + ); + + ImGui::TextUnformatted(a.name); + ImGui::PopStyleColor(); + + const char* statusLabel = a.unlocked ? "[Unlocked]" : "[Locked]"; + ImGui::SameLine( + ImGui::GetContentRegionMax().x - + ImGui::CalcTextSize(statusLabel).x + ); + + if (a.unlocked) { + ImGui::TextColored(ImVec4(0.2f, 0.8f, 0.2f, 1.0f), "%s", statusLabel); + } else { + ImGui::TextColored(ImVec4(0.8f, 0.2f, 0.2f, 1.0f), "%s", statusLabel); + } + + ImGui::TextDisabled("%s", a.description); + + if (a.isCounter) { + const float fraction = a.goal > 0 ? (float)(a.progress) / (float)(a.goal) : 1.0f; + const std::string overlay = fmt::format("{} / {}", a.progress, a.goal); + ImGui::PushStyleColor( + ImGuiCol_PlotHistogram, + a.unlocked ? + ImVec4(0.4f, 0.7f, 0.1f, 1.0f) : + ImVec4(0.2f, 0.45f, 0.8f, 1.0f) + ); + ImGui::ProgressBar(fraction, ImVec2(-1.0f, 0.0f), overlay.c_str()); + ImGui::PopStyleColor(); + } + + ImGui::Spacing(); + ImGui::PopID(); + } + + ImGui::EndChild(); + ImGui::EndTabItem(); + } + } + ImGui::EndTabBar(); + } + + ImGui::Separator(); + ImGui::Spacing(); + + if (ImGui::Button("Clear All Achievements")) { + ImGui::OpenPopup("##confirm_clear"); + } + + if (ImGui::BeginPopup("##confirm_clear")) { + ImGui::Text("Reset all achievement progress?"); + ImGui::Spacing(); + if (ImGui::Button("Yes, reset all")) { + AchievementSystem::get().clearAll(); + ImGui::CloseCurrentPopup(); + } + ImGui::SameLine(); + if (ImGui::Button("Cancel")) { + ImGui::CloseCurrentPopup(); + } + ImGui::EndPopup(); + } + + ImGui::End(); +} + +} // namespace dusk diff --git a/src/dusk/imgui/ImGuiAchievements.hpp b/src/dusk/imgui/ImGuiAchievements.hpp new file mode 100644 index 0000000000..5ee77373fc --- /dev/null +++ b/src/dusk/imgui/ImGuiAchievements.hpp @@ -0,0 +1,23 @@ +#pragma once + +#include +#include + +namespace dusk { + +class ImGuiAchievements { +public: + void draw(bool& open); + void notify(std::string name); + +private: + void showNotification(); + + std::string m_notifyName; + float m_notifyTimer = 0.f; + std::queue m_notifyQueue; + static constexpr float NOTIFY_DURATION = 4.0f; + static constexpr float NOTIFY_FADE_TIME = 0.5f; +}; + +} // 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 f71dc8c278..6bca83482e 100644 --- a/src/dusk/imgui/ImGuiConsole.cpp +++ b/src/dusk/imgui/ImGuiConsole.cpp @@ -3,23 +3,27 @@ #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/achievements.h" +#include "dusk/audio/DuskAudioSystem.h" #include "dusk/config.hpp" +#include "dusk/dusk.h" +#include "dusk/frame_interpolation.h" +#include "dusk/livesplit.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 +34,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; } @@ -38,6 +66,10 @@ namespace dusk { ImGui::TextUnformatted(text.data(), text.data() + text.size()); } + void DuskToast(std::string_view message, float duration) { + g_imguiConsole.AddToast(message, duration); + } + void ImGuiTextCenter(std::string_view text) { ImGui::NewLine(); float fontSize = ImGui::CalcTextSize( @@ -208,10 +240,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) { @@ -224,6 +301,15 @@ namespace dusk { UpdateSettings(); + AchievementSystem::get().tick(); + while (AchievementSystem::get().hasPendingUnlock()) { + if (getSettings().game.enableAchievementNotifications) { + m_menuTools.notifyAchievement(AchievementSystem::get().consumePendingUnlock()); + } else { + AchievementSystem::get().consumePendingUnlock(); + } + } + if ((ImGui::IsKeyDown(ImGuiKey_LeftCtrl) || ImGui::IsKeyDown(ImGuiKey_RightCtrl)) && ImGui::IsKeyPressed(ImGuiKey_R)) { @@ -234,37 +320,76 @@ 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 (ImGui::IsKeyPressed(ImGuiKey_Escape) && getSettings().video.enableFullscreen) { + ImGuiMenuGame::ToggleFullscreen(); } 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; + if (getSettings().game.liveSplitEnabled) { + dusk::speedrun::connectLiveSplit(); + } } + UpdateDragScroll(); + m_menuGame.windowControllerConfig(); m_menuGame.windowInputViewer(); - if (dusk::IsGameLaunched) { + m_menuGame.drawSpeedrunTimerOverlay(); + + if (getSettings().game.liveSplitEnabled) { + dusk::speedrun::updateLiveSplit(); + if (dusk::speedrun::consumeConnectedEvent()) + AddToast("LiveSplit connected"); + else if (dusk::speedrun::consumeDisconnectedEvent()) + AddToast("LiveSplit disconnected"); + } + + if (dusk::IsGameLaunched && !dusk::getSettings().game.speedrunMode) { m_menuTools.ShowDebugOverlay(); m_menuTools.ShowCameraOverlay(); m_menuTools.ShowProcessManager(); @@ -275,14 +400,21 @@ namespace dusk { m_menuTools.ShowPlayerInfo(); m_menuTools.ShowAudioDebug(); m_menuTools.ShowSaveEditor(); + m_menuTools.ShowStateShare(); } - m_menuTools.ShowStateShare(); + m_menuTools.ShowAchievements(); 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(); @@ -296,6 +428,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; @@ -391,6 +574,10 @@ namespace dusk { return false; } + void ImGuiConsole::AddToast(std::string_view message, float duration) { + m_toasts.emplace_back(std::string(message), duration); + } + void ImGuiConsole::ShowToasts() { if (m_toasts.empty()) { return; diff --git a/src/dusk/imgui/ImGuiConsole.hpp b/src/dusk/imgui/ImGuiConsole.hpp index 0296dc24cc..a6c8b48fc7 100644 --- a/src/dusk/imgui/ImGuiConsole.hpp +++ b/src/dusk/imgui/ImGuiConsole.hpp @@ -1,27 +1,33 @@ #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(); static bool CheckMenuViewToggle(ImGuiKey key, bool& active); + void AddToast(std::string_view message, float duration = 3.f); private: struct Toast { @@ -32,13 +38,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 +59,7 @@ private: void ShowToasts(); void ShowPipelineProgress(); + void UpdateDragScroll(); }; extern ImGuiConsole g_imguiConsole; @@ -57,6 +71,7 @@ std::string BytesToString(size_t bytes); void SetOverlayWindowLocation(int corner); bool ShowCornerContextMenu(int& corner, int avoidCorner); void ImGuiStringViewText(std::string_view text); +void DuskToast(std::string_view message, float duration = 3.f); void ImGuiBeginGroupPanel(const char* name, const ImVec2& size); void ImGuiEndGroupPanel(); void ImGuiTextCenter(std::string_view text); 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..2a9bcc5c6e 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() { @@ -29,12 +29,14 @@ static void ApplyPresetHD() { s.game.fastTears.setValue(true); s.game.biggerWallets.setValue(true); s.game.invertCameraXAxis.setValue(true); + s.game.freeCamera.setValue(true); } static void ApplyPresetDusk() { ApplyPresetHD(); auto& s = getSettings(); + s.game.enableAchievementNotifications.setValue(true); s.game.enableQuickTransform.setValue(true); s.game.instantSaves.setValue(true); s.game.midnasLamentNonStop.setValue(true); diff --git a/src/dusk/imgui/ImGuiMenuEnhancements.cpp b/src/dusk/imgui/ImGuiMenuEnhancements.cpp deleted file mode 100644 index 516b924680..0000000000 --- a/src/dusk/imgui/ImGuiMenuEnhancements.cpp +++ /dev/null @@ -1,221 +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::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) { - 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"); - } - - if (getSettings().game.enableGyroRollgoal) { - config::ImGuiSliderFloat("Rollgoal Sensitivity", getSettings().game.gyroRollgoalSensitivity, 0.25f, 4.0f, "%.2f"); - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("Additional multiplier for scaling how strongly\n" - "the gyroscope affects the Rollgoal table."); - } - } - - if (getSettings().game.enableGyroAim) { - 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..d0da643b0c 100644 --- a/src/dusk/imgui/ImGuiMenuGame.cpp +++ b/src/dusk/imgui/ImGuiMenuGame.cpp @@ -1,24 +1,33 @@ #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 "dusk/livesplit.h" #include "m_Do/m_Do_controller_pad.h" #include "m_Do/m_Do_graphic.h" #include #include -#include "dusk/main.h" +#include "m_Do/m_Do_main.h" + +namespace { +constexpr int kInternalResolutionScaleMax = 12; +} // namespace + +namespace aurora::gx { +extern bool enableLodBias; +} namespace dusk { void ImGuiMenuGame::ToggleFullscreen() { @@ -30,116 +39,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 +53,7 @@ namespace dusk { JUTGamePad::C3ButtonReset::sResetSwitchPushing = true; } - if (ImGui::MenuItem("Exit")) { + if (!IsMobile && ImGui::MenuItem("Exit")) { dusk::IsRunning = false; } @@ -155,6 +61,449 @@ 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); + + config::ImGuiCheckbox("Enable Mini-Map Shadows", getSettings().game.enableMapBackground); + + 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."); + } + + config::ImGuiCheckbox("Enable Rotating Link Doll", getSettings().game.enableLinkDollRotation); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Enables rotating Link in the collection menu with the C-Stick"); + } + + ImGui::SeparatorText("Difficulty"); + + ImGui::BeginDisabled(getSettings().game.speedrunMode); + 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::EndDisabled(); + + 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::SeparatorText("Speedrunning"); + if (config::ImGuiCheckbox("Speedrun Mode", getSettings().game.speedrunMode)) { + resetForSpeedrunMode(); + } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Enables Speedrunning options, while restricting certain gameplay modifiers."); + } + + ImGui::BeginDisabled(!getSettings().game.speedrunMode); + bool prevLiveSplit = getSettings().game.liveSplitEnabled; + config::ImGuiCheckbox("LiveSplit Connection", getSettings().game.liveSplitEnabled); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Connect to LiveSplit server on localhost:16834."); + } + ImGui::EndDisabled(); + + if ((bool)getSettings().game.liveSplitEnabled != prevLiveSplit) { + if (getSettings().game.liveSplitEnabled) { + dusk::speedrun::connectLiveSplit(); + } else { + dusk::speedrun::disconnectLiveSplit(); + DuskToast("LiveSplit disconnected", 3.f); + } + } + + ImGui::EndMenu(); + } + } + + void ImGuiMenuGame::drawCheatsMenu() { + if (ImGui::BeginMenu("Cheats")) { + ImGui::BeginDisabled(getSettings().game.speedrunMode); + + 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); + config::ImGuiCheckbox("No Item Timer", getSettings().game.enableIndefiniteItemDrops); + ImGui::SetItemTooltip("Item drops such as Rupees, Hearts, etc. will never disappear after they drop."); + + 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::EndDisabled(); + + 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("Free Camera", getSettings().game.freeCamera); + + if (getSettings().game.freeCamera) { + config::ImGuiCheckbox("Invert Camera X Axis", getSettings().game.invertCameraXAxis); + config::ImGuiCheckbox("Invert Camera Y Axis", getSettings().game.invertCameraYAxis); + config::ImGuiSliderFloat("Free Camera Sensitivity", getSettings().game.freeCameraSensitivity, 0.5f, 2.0f, "%.1f"); + } else { + 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"); + + ImGui::BeginDisabled(getSettings().game.speedrunMode); + config::ImGuiCheckbox("Turbo Key", getSettings().game.enableTurboKeybind); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Hold TAB to increase game speed by up to 4x."); + } + ImGui::EndDisabled(); + + ImGui::Checkbox("Show Input Viewer", &m_showInputViewer); + + ImGui::EndMenu(); + } + } + + void ImGuiMenuGame::drawInterfaceMenu() { + if (ImGui::BeginMenu("Interface")) { + config::ImGuiCheckbox("Achievement Notifications", getSettings().game.enableAchievementNotifications); + 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 +653,7 @@ namespace dusk { m_controllerConfig.m_pendingButtonMapping = nullptr; m_controllerConfig.m_pendingPort = -1; PADBlockInput(false); + PADSerializeMappings(); } } @@ -316,6 +666,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 +675,7 @@ namespace dusk { m_controllerConfig.m_pendingAxisMapping = nullptr; m_controllerConfig.m_pendingPort = -1; PADBlockInput(false); + PADSerializeMappings(); } } } @@ -400,11 +752,6 @@ namespace dusk { // if "None" selected PADClearPort(m_controllerConfig.m_selectedPort); } - } - - // save mappings button - ImGui::SameLine(); - if (ImGui::Button("Save")) { PADSerializeMappings(); } @@ -412,6 +759,7 @@ namespace dusk { ImGui::SameLine(); if (ImGui::Button("Default")) { PADRestoreDefaultMapping(m_controllerConfig.m_selectedPort); + PADSerializeMappings(); } // buttons panel @@ -508,6 +856,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 +868,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 +931,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 +994,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(); } } } @@ -651,15 +1003,124 @@ namespace dusk { ImGui::SameLine(); // Options panel - ImGuiBeginGroupPanel("Options", ImVec2(150 * scale, 20 * scale)); + ImGuiBeginGroupPanel("Options", ImVec2(150 * scale, -1)); 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(); + } + } + + if (PADSupportsRumbleIntensity(m_controllerConfig.m_selectedPort)) { + ImGuiBeginGroupPanel("Rumble Intensity", ImVec2(150 * scale, -1)); + u16 low; + u16 high; + (void)PADGetRumbleIntensity(m_controllerConfig.m_selectedPort, &low, &high); + float fLow = (static_cast(low) / 32767.f) * 100.f; + bool changed = ImGui::SliderFloat("Low", &fLow, 0.f, 100.f, "%.0f%%"); + float fHigh = (static_cast(high) / 32767.f) * 100.f; + changed |= ImGui::SliderFloat("High", &fHigh, 0.f, 100.f, "%.0f%%"); + if (changed) { + PADSetRumbleIntensity(m_controllerConfig.m_selectedPort, + static_cast((fLow / 100) * 32767), + static_cast((fHigh / 100) * 32767)); + PADSerializeMappings(); + } + if (ImGui::Button(fmt::format("{0}...##rumbleTest", m_controllerConfig.m_isRumbling ? "Stop": "Test").c_str(), {-1, 0})) { + PADControlMotor(m_controllerConfig.m_selectedPort, !m_controllerConfig.m_isRumbling ? PAD_MOTOR_RUMBLE : PAD_MOTOR_STOP_HARD); + m_controllerConfig.m_isRumbling ^= 1; + } + ImGuiEndGroupPanel(); } - ImGuiEndGroupPanel(); ImGui::End(); } + + static std::string GetFormattedTime(OSTime ticks) { + OSCalendarTime time; + OSTicksToCalendarTime(ticks, &time); + + return fmt::format("{0:02}:{1:02}:{2:02}.{3:03}", time.hour, time.min, time.sec, time.msec); + } + + void ImGuiMenuGame::resetForSpeedrunMode() { + // reset settings that should be off for speedrun mode + mDoMain::developmentMode = -1; + + getSettings().game.damageMultiplier.setValue(1); + getSettings().game.instantDeath.setValue(false); + getSettings().game.noHeartDrops.setValue(false); + + getSettings().game.infiniteHearts.setValue(false); + getSettings().game.infiniteArrows.setValue(false); + getSettings().game.infiniteBombs.setValue(false); + getSettings().game.infiniteOil.setValue(false); + getSettings().game.infiniteOxygen.setValue(false); + getSettings().game.infiniteRupees.setValue(false); + getSettings().game.enableIndefiniteItemDrops.setValue(false); + + getSettings().game.moonJump.setValue(false); + getSettings().game.superClawshot.setValue(false); + getSettings().game.alwaysGreatspin.setValue(false); + getSettings().game.enableFastIronBoots.setValue(false); + getSettings().game.canTransformAnywhere.setValue(false); + getSettings().game.fastSpinner.setValue(false); + getSettings().game.freeMagicArmor.setValue(false); + + getSettings().game.enableTurboKeybind.setValue(false); + } + + SpeedrunInfo m_speedrunInfo; + + void ImGuiMenuGame::drawSpeedrunTimerOverlay() { + if (!getSettings().game.speedrunMode) { + return; + } + + // L+R+A+Start to reset timer + if (mDoCPd_c::getHoldL(PAD_1) && mDoCPd_c::getHoldR(PAD_1) && mDoCPd_c::getHoldA(PAD_1) && mDoCPd_c::getTrigStart(PAD_1)) { + m_speedrunInfo.reset(); + } + + // L+R+A+Z to manually stop timer + if (mDoCPd_c::getHoldL(PAD_1) && mDoCPd_c::getHoldR(PAD_1) && mDoCPd_c::getHoldA(PAD_1) && mDoCPd_c::getTrigZ(PAD_1)) { + if (m_speedrunInfo.m_isRunStarted) { + m_speedrunInfo.m_endTimestamp = OSGetTime() - m_speedrunInfo.m_startTimestamp; + m_speedrunInfo.m_isRunStarted = false; + } + } + + ImGui::SetNextWindowBgAlpha(0.65f); + ImGuiWindowFlags flags = + ImGuiWindowFlags_NoResize + | ImGuiWindowFlags_NoDocking + | ImGuiWindowFlags_NoTitleBar + | ImGuiWindowFlags_NoScrollbar; + + if (ImGui::Begin("##SpeedrunTimerWindow", nullptr, flags)) { + OSTime elapsedTime = 0; + if (m_speedrunInfo.m_isRunStarted) { + elapsedTime = OSGetTime() - m_speedrunInfo.m_startTimestamp; + } else if (m_speedrunInfo.m_endTimestamp != 0) { + elapsedTime = m_speedrunInfo.m_endTimestamp; + } + + ImGui::Text("RTA"); + ImGui::SameLine(60.0f); + ImGuiStringViewText(GetFormattedTime(elapsedTime)); + + if (!m_speedrunInfo.m_isPauseIGT) { + m_speedrunInfo.m_igtTimer = elapsedTime - m_speedrunInfo.m_totalLoadTime; + } + + ImGui::Text("IGT"); + ImGui::SameLine(60.0f); + ImGuiStringViewText(GetFormattedTime(m_speedrunInfo.m_igtTimer)); + } + ImGui::End(); + } } diff --git a/src/dusk/imgui/ImGuiMenuGame.hpp b/src/dusk/imgui/ImGuiMenuGame.hpp index 4d51cbc865..e54541a010 100644 --- a/src/dusk/imgui/ImGuiMenuGame.hpp +++ b/src/dusk/imgui/ImGuiMenuGame.hpp @@ -8,6 +8,39 @@ #include "imgui.h" namespace dusk { + struct SpeedrunInfo { + void startRun() { + m_isRunStarted = true; + m_startTimestamp = OSGetTime(); + } + + void stopRun() { + m_isRunStarted = false; + m_endTimestamp = OSGetTime() - m_startTimestamp; + } + + void reset() { + m_isRunStarted = false; + m_startTimestamp = 0; + m_endTimestamp = 0; + m_isPauseIGT = false; + m_loadStartTimestamp = 0; + m_totalLoadTime = 0; + m_igtTimer = 0; + } + + bool m_isRunStarted = false; + OSTime m_startTimestamp = 0; + OSTime m_endTimestamp = 0; + + bool m_isPauseIGT = false; + OSTime m_loadStartTimestamp = 0; + OSTime m_totalLoadTime = 0; + OSTime m_igtTimer = 0; + }; + + extern SpeedrunInfo m_speedrunInfo; + class ImGuiMenuGame { public: ImGuiMenuGame(); @@ -15,16 +48,27 @@ namespace dusk { void windowInputViewer(); void windowControllerConfig(); + void drawSpeedrunTimerOverlay(); static void ToggleFullscreen(); + static void resetForSpeedrunMode(); + private: + void drawAudioMenu(); + void drawInputMenu(); + void drawGraphicsMenu(); + void drawGameplayMenu(); + void drawCheatsMenu(); + void drawInterfaceMenu(); + struct { int m_selectedPort = 0; bool m_isReading = false; PADButtonMapping* m_pendingButtonMapping = nullptr; PADAxisMapping* m_pendingAxisMapping = nullptr; int m_pendingPort = -1; + bool m_isRumbling = false; } m_controllerConfig; bool m_showControllerConfig = false; @@ -33,6 +77,8 @@ namespace dusk { bool m_showInputViewerGyro = false; int m_inputOverlayCorner = 3; std::string m_controllerName; + + bool m_showTimerWindow = false; }; } diff --git a/src/dusk/imgui/ImGuiMenuTools.cpp b/src/dusk/imgui/ImGuiMenuTools.cpp index 9bb7670e88..2e3738aab9 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,11 +16,67 @@ #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::BeginDisabled(getSettings().game.speedrunMode); + + 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::EndDisabled(); + + if (!dusk::IsGameLaunched) { + ImGui::EndDisabled(); + } + + ImGui::MenuItem("Achievements", nullptr, &m_showAchievements); + +#if DUSK_CAN_OPEN_DATA_FOLDER + ImGui::Separator(); + if (ImGui::MenuItem("Open Data Folder")) { + OpenDataFolder(); + } +#endif + + ImGui::EndMenu(); + } + if (ImGui::BeginMenu("Debug")) { + ImGui::BeginDisabled(getSettings().game.speedrunMode); + bool developmentMode = mDoMain::developmentMode == 1; if (ImGui::Checkbox("Development Mode", &developmentMode)) { mDoMain::developmentMode = developmentMode ? 1 : -1; @@ -28,6 +85,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,9 +115,6 @@ 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); @@ -62,6 +125,9 @@ namespace dusk { } ImGui::MenuItem("OSReport Force", nullptr, &OSReportReallyForceEnable); + + ImGui::EndDisabled(); + ImGui::EndMenu(); } } @@ -86,7 +152,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(); @@ -153,7 +221,7 @@ namespace dusk { ImGui::Text("Link"); ImGuiStringViewText( player != nullptr - ? fmt::format("Position: {: .2f}, {: .2f}, {: .2f}\n", player->current.pos.x, player->current.pos.y, player->current.pos.z) + ? fmt::format("Position: {: .4f}, {: .4f}, {: .4f}\n", player->current.pos.x, player->current.pos.y, player->current.pos.z) : "Position: ?, ?, ?\n" ); @@ -165,7 +233,7 @@ namespace dusk { ImGuiStringViewText( player != nullptr - ? fmt::format("Speed: {0}\n", player->speedF) + ? fmt::format("Speed: {: .4f}\n", player->speedF) : "Speed: ?\n" ); @@ -173,7 +241,7 @@ namespace dusk { ImGui::Text("Epona"); ImGuiStringViewText( horse != nullptr - ? fmt::format("Position: {: .2f}, {: .2f}, {: .2f}\n", horse->current.pos.x, horse->current.pos.y, horse->current.pos.z) + ? fmt::format("Position: {: .4f}, {: .4f}, {: .4f}\n", horse->current.pos.x, horse->current.pos.y, horse->current.pos.z) : "Position: ?, ?, ?\n" ); @@ -185,7 +253,7 @@ namespace dusk { ImGuiStringViewText( horse != nullptr - ? fmt::format("Speed: {0}\n", horse->speedF) + ? fmt::format("Speed: {: .4f}\n", horse->speedF) : "Speed: ?\n" ); @@ -195,4 +263,12 @@ namespace dusk { ImGui::End(); ImGui::PopFont(); } + + void ImGuiMenuTools::ShowAchievements() { + m_achievementsWindow.draw(m_showAchievements); + } + + void ImGuiMenuTools::notifyAchievement(std::string name) { + m_achievementsWindow.notify(std::move(name)); + } } diff --git a/src/dusk/imgui/ImGuiMenuTools.hpp b/src/dusk/imgui/ImGuiMenuTools.hpp index e48aab2742..de94ad2d8f 100644 --- a/src/dusk/imgui/ImGuiMenuTools.hpp +++ b/src/dusk/imgui/ImGuiMenuTools.hpp @@ -5,6 +5,7 @@ #include #include "imgui.h" +#include "ImGuiAchievements.hpp" #include "ImGuiSaveEditor.hpp" #include "ImGuiStateShare.hpp" @@ -26,6 +27,8 @@ namespace dusk { void ShowAudioDebug(); void ShowSaveEditor(); void ShowStateShare(); + void ShowAchievements(); + void notifyAchievement(std::string name); private: bool m_showDebugOverlay = false; @@ -65,6 +68,9 @@ namespace dusk { bool m_showStateShare = false; ImGuiStateShare m_stateShare; + + bool m_showAchievements = false; + ImGuiAchievements m_achievementsWindow; }; } diff --git a/src/dusk/imgui/ImGuiPreLaunchWindow.cpp b/src/dusk/imgui/ImGuiPreLaunchWindow.cpp index c24dbd8247..a0006f0ad3 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" @@ -22,6 +22,10 @@ typedef void (ImGuiPreLaunchWindow::*drawFunc)(); drawFunc drawTable[2] = {&ImGuiPreLaunchWindow::drawMainMenu, &ImGuiPreLaunchWindow::drawOptions}; +static constexpr std::array skLanguageNames = { + "English", "German", "French", "Spanish", "Italian" +}; + static constexpr std::array skGameDiscFileFilters{{ {"Game Disc Images", "iso;gcm;ciso;gcz;nfs;rvz;wbfs;wia;tgc"}, {"All Files", "*"}, @@ -46,32 +50,36 @@ static std::string ShowIsoInvalidError(const iso::ValidationError code) { } } -void fileDialogCallback(void* userdata, const char* const* filelist, [[maybe_unused]] int filter) { - 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 - self->m_selectedIsoPath.clear(); - self->m_errorString = fmt::format("File dialog error: {}", SDL_GetError()); +static std::string_view card_type_name(CARDFileType type) { + switch (type) { + case CARD_GCIFOLDER: + return "GCI Folder"sv; + case CARD_RAWIMAGE: + return "Card Image"sv; + default: + return ""sv; } } +void fileDialogCallback(void* userdata, const char* path, const char* error) { + auto* self = static_cast(userdata); + if (error != nullptr) { + self->m_selectedIsoPath.clear(); + self->m_errorString = fmt::format("File dialog error: {}", error); + return; + } + + if (path == nullptr) { + self->m_selectedIsoPath.clear(); + return; + } + + self->m_selectedIsoPath = path; + self->m_isPal = iso::isPal(path); + getSettings().backend.isoPath.setValue(self->m_selectedIsoPath); + config::Save(); +} + ImGuiPreLaunchWindow::ImGuiPreLaunchWindow() = default; bool ImGuiPreLaunchWindow::isSelectedPathValid() const { @@ -85,6 +93,7 @@ bool ImGuiPreLaunchWindow::isSelectedPathValid() const { void ImGuiPreLaunchWindow::draw() { if (m_IsFirstDraw) { m_selectedIsoPath = getSettings().backend.isoPath; + m_isPal = !m_selectedIsoPath.empty() && iso::isPal(m_selectedIsoPath.c_str()); m_initialGraphicsBackend = getSettings().backend.graphicsBackend; m_IsFirstDraw = false; } @@ -144,9 +153,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")) { @@ -186,10 +195,24 @@ 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); + if (ImGui::Button(m_selectedIsoPath == "" ? "Set" : "Change")) { + ShowFileSelect(&fileDialogCallback, this, aurora::window::get_sdl_window(), + skGameDiscFileFilters.data(), int(skGameDiscFileFilters.size()), nullptr, + false); + } + + if (m_isPal) { + auto selectedLanguage = getSettings().game.language.getValue(); + if (ImGui::BeginCombo("Language", skLanguageNames[static_cast(selectedLanguage)])) { + for (u8 i = 0; i < skLanguageNames.size(); ++i) { + if (ImGui::Selectable(skLanguageNames[i])) { + getSettings().game.language.setValue(static_cast(i)); + config::Save(); + } + } + + ImGui::EndCombo(); + } } AuroraBackend configuredBackend = BACKEND_AUTO; @@ -224,6 +247,23 @@ void ImGuiPreLaunchWindow::drawOptions() { if (configuredBackendId != m_initialGraphicsBackend) { ImGui::TextDisabled("Restart Required"); } + auto curFileType = (CARDFileType)getSettings().backend.cardFileType.getValue(); + + if (ImGui::BeginCombo("Save File Type", card_type_name(curFileType).data())) { + + if (ImGui::Selectable("GCI Folder", curFileType == CARD_GCIFOLDER)) { + getSettings().backend.cardFileType.setValue(CARD_GCIFOLDER); + config::Save(); + } + + if (ImGui::Selectable("Card Image", curFileType == CARD_RAWIMAGE)) { + getSettings().backend.cardFileType.setValue(CARD_RAWIMAGE); + config::Save(); + } + + ImGui::EndCombo(); + } + ImGui::EndChild(); } diff --git a/src/dusk/imgui/ImGuiPreLaunchWindow.hpp b/src/dusk/imgui/ImGuiPreLaunchWindow.hpp index 5d16a6ba0b..6cb078a228 100644 --- a/src/dusk/imgui/ImGuiPreLaunchWindow.hpp +++ b/src/dusk/imgui/ImGuiPreLaunchWindow.hpp @@ -18,5 +18,6 @@ public: std::string m_selectedIsoPath; std::string m_errorString; + bool m_isPal = false; }; } // namespace dusk 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 2c679308a2..cc89d37275 100644 --- a/src/dusk/imgui/ImGuiSaveEditor.cpp +++ b/src/dusk/imgui/ImGuiSaveEditor.cpp @@ -4,11 +4,13 @@ #include "ImGuiConsole.hpp" #include "ImGuiSaveEditor.hpp" +#include "ImGuiEventFlags.hpp" #include "d/d_com_inf_game.h" #include "d/d_item_data.h" #include "d/d_meter2_info.h" #include "d/d_save.h" +#include "d/actor/d_a_player.h" #include @@ -578,20 +580,21 @@ namespace dusk { if (ImGui::BeginCombo("Clothes", itemMap.find(statusA.mSelectEquip[0])->second.m_name.c_str())) { - if (ImGui::Selectable("None")) { - statusA.mSelectEquip[0] = dItemNo_NONE_e; - } if (ImGui::Selectable("Ordon Clothes")) { - statusA.mSelectEquip[0] = dItemNo_WEAR_CASUAL_e; + dMeter2Info_setCloth(dItemNo_WEAR_CASUAL_e, false); + daPy_getPlayerActorClass()->setClothesChange(0); } if (ImGui::Selectable("Hero's Clothes")) { - statusA.mSelectEquip[0] = dItemNo_WEAR_KOKIRI_e; + dMeter2Info_setCloth(dItemNo_WEAR_KOKIRI_e, false); + daPy_getPlayerActorClass()->setClothesChange(0); } if (ImGui::Selectable("Zora Armor")) { - statusA.mSelectEquip[0] = dItemNo_WEAR_ZORA_e; + dMeter2Info_setCloth(dItemNo_WEAR_ZORA_e, false); + daPy_getPlayerActorClass()->setClothesChange(0); } if (ImGui::Selectable("Magic Armor")) { - statusA.mSelectEquip[0] = dItemNo_ARMOR_e; + dMeter2Info_setCloth(dItemNo_ARMOR_e, false); + daPy_getPlayerActorClass()->setClothesChange(0); } ImGui::EndCombo(); } @@ -1344,6 +1347,51 @@ namespace dusk { } } + template + concept FlagIter = requires(T t) { + ++t; + --t; + t + 1; + t < t; + { t->flagID } -> std::convertible_to; + }; + + template + concept FlagTester = requires(T t, u16 flagID) { + { t(flagID) } -> std::convertible_to; + }; + + static void sortByFlags(FlagIter auto begin, FlagIter auto end, FlagTester auto&& flagTester) { + if (begin == end) return; + + FlagIter auto fullEnd = end; + + // We want to find the location of where we can swap our `On` flags to. + // We're gonna put the `Off` bits first, and the `On` bits last. 0 < 1 + // We can achieve this by skipping all the `On` bits at the end. + + // backtrack until we find a bit that is off + while (begin < --end && flagTester(end->flagID)) { + // move the end pointer back while we find on bits + } + + // end should now be pointing to a bit that is off + while (begin < end) { + // if there's a flag that's on + if (flagTester(begin->flagID)) { + // move it to the end + std::rotate(begin, begin + 1, fullEnd); + // move back the end of where we're checking + --end; + // begin will now point to the next piece of data + // because we've rotated the data >= begin to the left + } else { + // not on, check next flag + ++begin; + } + } + } + void ImGuiSaveEditor::drawFlagsTab() { if (ImGui::TreeNode("Current Region Flags")) { dSv_memBit_c& membit = g_dComIfG_gameInfo.info.mMemory.mBit; @@ -1419,20 +1467,141 @@ 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)) + { + const auto column = sort->Specs[0].ColumnIndex; + const auto direction = sort->Specs[0].SortDirection; + + // if we're sorting by flags, do special sort, regular sort is bad for sorting bools + // it can swap values that are the same, and that causes constant reordering + if (column == COLUMN_FLAG) { + const auto testEventFunc = [&event](u16 flag) -> bool { return event.isEventBit(flag); }; + + if (direction == ImGuiSortDirection_Ascending) { + sortByFlags(std::begin(duskImguiEventFlags), + std::end(duskImguiEventFlags), testEventFunc); + } else { + sortByFlags(std::rbegin(duskImguiEventFlags), + std::rend(duskImguiEventFlags), testEventFunc); + } + } else { + const auto cmp = [column](const duskImguiEventFlagEntry& l, + const duskImguiEventFlagEntry& r) -> bool { + switch (column) { + case COLUMN_NAME: return l.flagName < r.flagName; + case COLUMN_LOC: return l.location < r.location; + case COLUMN_DESC: return l.description < r.description; + default: return false; + } + }; + + 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(); + ImGuiStringViewText(e.flagName); + ImGui::TableNextColumn(); + ImGuiStringViewText(e.location); + ImGui::TableNextColumn(); + ImGuiStringViewText(e.description); + } + 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(); } diff --git a/src/dusk/imgui/ImGuiStateShare.cpp b/src/dusk/imgui/ImGuiStateShare.cpp index 172aad17a5..adbe5b6c67 100644 --- a/src/dusk/imgui/ImGuiStateShare.cpp +++ b/src/dusk/imgui/ImGuiStateShare.cpp @@ -5,14 +5,24 @@ #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 "dusk/settings.h" +#include "f_op/f_op_overlap_mng.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 +33,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 +106,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 +140,272 @@ 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; + dusk::getTransientSettings().stateShareLoadActive = true; + m_stateSharePeekSeen = false; + dComIfGp_setNextStage(pkt.stageName, spawnPoint, pkt.roomNo, pkt.layer, 0.0f, 0, 1, 0, 0, 1, 3); - 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 (dusk::getTransientSettings().stateShareLoadActive) { + if (fopOvlpM_IsPeek()) { + m_stateSharePeekSeen = true; + } else if (m_stateSharePeekSeen) { + dusk::getTransientSettings().stateShareLoadActive = false; + m_stateSharePeekSeen = false; + } + } + } - if (!open) + if (!m_loaded) { + loadStatesFile(); + } + + if (!m_pendingMergePath.empty()) { + mergeFromFile(m_pendingMergePath); + m_pendingMergePath.clear(); + } + + if (!open) { return; + } - if (!ImGui::Begin("State Share", &open, ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoFocusOnAppearing | ImGuiWindowFlags_NoNav)) { + ImGui::SetNextWindowSizeConstraints(ImVec2(400, 0), ImVec2(FLT_MAX, FLT_MAX)); + if (!ImGui::Begin("State Manager", &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 bool loadInProgress = dusk::getTransientSettings().stateShareLoadActive; + + 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 || loadInProgress) { ImGui::BeginDisabled(); } + if (ImGui::Button("Load")) { + applyEncodedState(m_states[i].encoded, m_states[i].name); + } + if (!gameRunning || loadInProgress) { 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 +417,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..a2dc681833 100644 --- a/src/dusk/imgui/ImGuiStateShare.hpp +++ b/src/dusk/imgui/ImGuiStateShare.hpp @@ -4,21 +4,39 @@ #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; + bool m_stateSharePeekSeen = 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/iso_validate.cpp b/src/dusk/iso_validate.cpp index 755b4eede0..e83b3fd34e 100644 --- a/src/dusk/iso_validate.cpp +++ b/src/dusk/iso_validate.cpp @@ -17,8 +17,14 @@ constexpr const char* TP_GAME_IDS[] = { "RZDK01", // Wii KOR }; +constexpr const char* PAL_GAME_IDS[] = { + "GZ2P01", // GCN PAL + "RZDP01", // Wii PAL +}; + constexpr const char* SUPPORTED_TP_GAME_IDS[] = { "GZ2E01", // GCN USA + "GZ2P01", // GCN PAL }; template @@ -123,4 +129,30 @@ ValidationError validate(const char* path) { return ValidationError::Success; } +bool isPal(const char* path) { + NodHandleWrapper disc; + + const auto sdlStream = SDL_IOFromFile(path, "rb"); + if (sdlStream == nullptr) { + return false; + } + + const NodDiscStream nod_stream{ + .user_data = sdlStream, + .read_at = StreamReadAt, + .stream_len = StreamLength, + .close = StreamClose, + }; + + if (nod_disc_open_stream(&nod_stream, nullptr, &disc.handle) != NOD_RESULT_OK || disc.handle == nullptr) { + return false; + } + + NodDiscHeader header{}; + if (nod_disc_header(disc.handle, &header) != NOD_RESULT_OK) { + return false; + } + + return matches(header.game_id, PAL_GAME_IDS); +} } // namespace dusk::iso \ No newline at end of file diff --git a/src/dusk/iso_validate.hpp b/src/dusk/iso_validate.hpp index da1ef1f2a6..d961f052cd 100644 --- a/src/dusk/iso_validate.hpp +++ b/src/dusk/iso_validate.hpp @@ -13,6 +13,7 @@ namespace dusk::iso { }; ValidationError validate(const char* path); + bool isPal(const char* path); } #endif // DUSK_ISO_VALIDATE_HPP diff --git a/src/dusk/livesplit.cpp b/src/dusk/livesplit.cpp new file mode 100644 index 0000000000..cec765f223 --- /dev/null +++ b/src/dusk/livesplit.cpp @@ -0,0 +1,183 @@ +#if _WIN32 + #include + #include + using socket_t = SOCKET; + static void closeSocket(socket_t s) { closesocket(s); } +#else + #include + #include + #include + #include + #include + #include + using socket_t = int; + static void closeSocket(socket_t s) { close(s); } + #ifndef INVALID_SOCKET + #define INVALID_SOCKET -1 + #endif +#endif + +#include +#include "dusk/livesplit.h" +#include "f_op/f_op_overlap_mng.h" + +namespace dusk::speedrun { + +static bool running = false; +static uint64_t frameCount = 0; +static socket_t sock = INVALID_SOCKET; +static bool wasLoading = false; +static bool connected = false; +static bool connectPending = false; +static bool disconnectPending = false; + +static void sendCmd(const char* cmd) { + if (sock == INVALID_SOCKET) { + return; + } + + char msg[64]; + int len = snprintf(msg, sizeof(msg), "%s\r\n", cmd); + + if (send(sock, msg, len, 0) >= 0) { + if (!connected) { + connected = connectPending = true; + } + + return; + } + +#if _WIN32 + int err = WSAGetLastError(); + if (err == WSAEWOULDBLOCK || err == WSAENOTCONN) { + return; + } +#else + if (errno == EAGAIN || errno == EWOULDBLOCK || errno == ENOTCONN) { + return; + } +#endif + + if (connected) disconnectPending = true; + closeSocket(sock); + sock = INVALID_SOCKET; + connected = connectPending = false; +} + +uint64_t getFrameCount() { + return frameCount; +} + +void onGameFrame() { + if (!running) { + return; + } + + bool loading = fopOvlpM_IsDoingReq() != 0; + + if (loading != wasLoading) { + sendCmd(loading ? "pausegametime" : "unpausegametime"); + wasLoading = loading; + } + + if (!loading) { + ++frameCount; + } +} + +void start() { + if (running) { + return; + } + + running = true; + frameCount = 0; + wasLoading = false; + sendCmd("initgametime"); + sendCmd("reset"); + sendCmd("starttimer"); +} + +void reset() { + running = false; + frameCount = 0; + wasLoading = false; + sendCmd("reset"); +} + +void connectLiveSplit(const char* host, int port) { +#if _WIN32 + WSADATA wd{}; WSAStartup(MAKEWORD(2, 2), &wd); +#endif + + if (sock != INVALID_SOCKET) { + closeSocket(sock); sock = INVALID_SOCKET; + } + + sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + + if (sock == INVALID_SOCKET) { + return; + } + +#if _WIN32 + u_long nb = 1; + ioctlsocket(sock, FIONBIO, &nb); +#else + fcntl(sock, F_SETFL, fcntl(sock, F_GETFL, 0) | O_NONBLOCK); +#endif + + sockaddr_in addr{}; addr.sin_family = AF_INET; + addr.sin_port = htons((uint16_t)port); + inet_pton(AF_INET, host, &addr.sin_addr); + connect(sock, (sockaddr*)&addr, sizeof(addr)); + sendCmd("initgametime"); +} + +void disconnectLiveSplit() { + if (sock != INVALID_SOCKET) { + closeSocket(sock); + sock = INVALID_SOCKET; + connected = false; + } +} + +bool consumeConnectedEvent() { bool v = connectPending; connectPending = false; return v; } +bool consumeDisconnectedEvent() { bool v = disconnectPending; disconnectPending = false; return v; } + +void updateLiveSplit() { + if (sock == INVALID_SOCKET) { + return; + } + + if (!connected) { + sendCmd("initgametime"); + return; + } + + if (!running) { + return; + } + + const uint64_t totalMs = frameCount * 1000 / 30; + const uint64_t totalSec = totalMs / 1000; + char cmd[32]; + + snprintf(cmd, sizeof(cmd), "setgametime %u:%02u:%02u.%03u", + (uint32_t)(totalSec / 3600), + (uint32_t)((totalSec / 60) % 60), + (uint32_t)(totalSec % 60), + (uint32_t)(totalMs % 1000) + ); + + sendCmd(cmd); +} + +void shutdown() { + disconnectLiveSplit(); +#if _WIN32 + WSACleanup(); +#endif +} + +} 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 fd6569ed2c..ee6ba663ca 100644 --- a/src/dusk/settings.cpp +++ b/src/dusk/settings.cpp @@ -20,6 +20,8 @@ UserSettings g_userSettings = { }, .game = { + .language { "game.language", GameLanguage::English }, + // Quality of Life .enableQuickTransform {"game.enableQuickTransform", false}, .hideTvSettingsScreen {"game.hideTvSettingsScreen", false}, @@ -35,19 +37,25 @@ 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}, + .enableLinkDollRotation = {"game.enableLinkDollRotation", false }, + .enableAchievementNotifications {"game.enableAchievementNotifications", 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}, + .enableMapBackground {"game.enableMapBackground", true}, // Audio .noLowHpSound {"game.noLowHpSound", false}, @@ -56,13 +64,29 @@ UserSettings g_userSettings = { // Input .enableGyroAim {"game.enableGyroAim", false}, .enableGyroRollgoal {"game.enableGyroRollgoal", false}, - .gyroAimSensitivityX {"game.gyroAimSensitivityX", 1.0f}, - .gyroAimSensitivityY {"game.gyroAimSensitivityY", 1.0f}, - .gyroRollgoalSensitivity {"game.gyroRollgoalSensitivity", 1.0f}, - .gyroAimInvertPitch {"game.gyroAimInvertPitch", false}, - .gyroAimInvertYaw {"game.gyroAimInvertYaw", 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}, + .freeCamera {"game.freeCamera", false}, + .invertCameraXAxis {"game.invertCameraXAxis", false}, + .invertCameraYAxis {"game.invertCameraYAxis", false}, + .freeCameraSensitivity {"game.freeCameraSensitivity", 1.0f}, // 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}, + .enableIndefiniteItemDrops {"game.enableIndefiniteItemDrops", 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}, @@ -72,7 +96,11 @@ UserSettings g_userSettings = { .restoreWiiGlitches {"game.restoreWiiGlitches", false}, // Controls - .enableTurboKeybind {"game.enableTurboKeybind", false} + .enableTurboKeybind {"game.enableTurboKeybind", false}, + + // Tools + .speedrunMode {"game.speedrunMode", false}, + .liveSplitEnabled {"game.liveSplitEnabled", false} }, .backend = { @@ -81,7 +109,9 @@ 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}, + .cardFileType {"backend.cardFileType", static_cast(CARD_GCIFOLDER)} } }; @@ -104,6 +134,7 @@ void registerSettings() { Register(g_userSettings.audio.enableReverb); // Game + Register(g_userSettings.game.language); Register(g_userSettings.game.enableQuickTransform); Register(g_userSettings.game.hideTvSettingsScreen); Register(g_userSettings.game.skipWarningScreen); @@ -117,31 +148,55 @@ 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.invertCameraYAxis); + Register(g_userSettings.game.freeCameraSensitivity); 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.enableMapBackground); Register(g_userSettings.game.enableFastIronBoots); Register(g_userSettings.game.canTransformAnywhere); Register(g_userSettings.game.freeMagicArmor); Register(g_userSettings.game.restoreWiiGlitches); + Register(g_userSettings.game.enableLinkDollRotation); + Register(g_userSettings.game.enableAchievementNotifications); Register(g_userSettings.game.noMissClimbing); Register(g_userSettings.game.noLowHpSound); Register(g_userSettings.game.midnasLamentNonStop); Register(g_userSettings.game.enableTurboKeybind); + Register(g_userSettings.game.speedrunMode); + Register(g_userSettings.game.liveSplitEnabled); 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.enableIndefiniteItemDrops); + 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.enableGyroRollgoal); - Register(g_userSettings.game.gyroAimSensitivityX); - Register(g_userSettings.game.gyroAimSensitivityY); - Register(g_userSettings.game.gyroRollgoalSensitivity); - Register(g_userSettings.game.gyroAimInvertPitch); - Register(g_userSettings.game.gyroAimInvertYaw); + 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.game.freeCamera); Register(g_userSettings.backend.isoPath); Register(g_userSettings.backend.graphicsBackend); @@ -149,6 +204,8 @@ void registerSettings() { Register(g_userSettings.backend.showPipelineCompilation); Register(g_userSettings.backend.wasPresetChosen); Register(g_userSettings.backend.enableCrashReporting); + Register(g_userSettings.backend.duskMenuOpen); + Register(g_userSettings.backend.cardFileType); } // Transient settings diff --git a/src/dusk/stubs.cpp b/src/dusk/stubs.cpp index 668541f49c..c6832d2cac 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; } @@ -1028,10 +1020,6 @@ void GXInitTexCacheRegion(GXTexRegion* region, GXBool is_32b_mipmap, u32 tmem_ev // XXX, this should be some struct? // GXRenderModeObj GXNtsc480IntDf; //GXRenderModeObj GXNtsc480Int; -void GXPeekZ(u16 x, u16 y, u32* z) { - STUB_LOG(); - *z = 0; -} void GXReadXfRasMetric(u32* xf_wait_in, u32* xf_wait_out, u32* ras_busy, u32* clocks) { STUB_LOG(); *xf_wait_in = 0; diff --git a/src/dusk/version.cpp b/src/dusk/version.cpp new file mode 100644 index 0000000000..e925dabaf8 --- /dev/null +++ b/src/dusk/version.cpp @@ -0,0 +1,82 @@ +#include "dusk/version.hpp" + +#include "dusk/logging.h" + +namespace dusk::version { + +using namespace std::string_view_literals; + +static bool versionInitialized; +static GameVersion gameVersion; +static DVDDiskID diskId; + +void init() { + versionInitialized = true; + + if (!DVDLowReadDiskID(&diskId, nullptr)) { + DuskLog.fatal("DVDLowReadDiskID failed to return instantly."); + } + + std::string_view company(diskId.company, sizeof(diskId.company)); + std::string_view game(diskId.gameName, sizeof(diskId.gameName)); + + if (company != "01"sv) { + DuskLog.fatal("Wrong company ID in disc: {}", company); + } + + if (game == "GZ2E"sv) { + gameVersion = GameVersion::GcnUsa; + } else if (game == "GZ2P") { + gameVersion = GameVersion::GcnPal; + } else { + // TODO: Handle remaining valid versions. + DuskLog.fatal("Unknown/unsupported game version in disc: {}", game); + } + + DuskLog.info("Loaded game disc is {}{}", game, company); +} + +bool isGcn() { + return getGameVersion() == GameVersion::GcnUsa + || getGameVersion() == GameVersion::GcnPal + || getGameVersion() == GameVersion::GcnJpn; +} + +bool isWii() { + return getGameVersion() == GameVersion::WiiUsaRev0 + || getGameVersion() == GameVersion::WiiUsa + || getGameVersion() == GameVersion::WiiPal + || getGameVersion() == GameVersion::WiiJpn + || getGameVersion() == GameVersion::WiiKor; +} + +bool isPalOrAtLeastWiiR2() { + return getGameVersion() == GameVersion::GcnPal || getGameVersion() >= GameVersion::WiiUsa; +} + +bool isRegionJpn() { + return getGameVersion() == GameVersion::WiiJpn || getGameVersion() == GameVersion::GcnJpn; +} + +bool isRegionPal() { + return getGameVersion() == GameVersion::WiiPal || getGameVersion() == GameVersion::GcnPal; +} + +bool isRegionUsa() { + return getGameVersion() == GameVersion::WiiUsa || getGameVersion() == GameVersion::WiiUsaRev0 + || getGameVersion() == GameVersion::GcnUsa; +} + +GameVersion getGameVersion() { + if (!versionInitialized) { + abort(); + } + + return gameVersion; +} + +const DVDDiskID& getDiskID() { + return diskId; +} + +} // namespace dusk::version \ No newline at end of file diff --git a/src/f_ap/f_ap_game.cpp b/src/f_ap/f_ap_game.cpp index 93a9148957..beec5847ef 100644 --- a/src/f_ap/f_ap_game.cpp +++ b/src/f_ap/f_ap_game.cpp @@ -15,6 +15,7 @@ #include "d/d_model.h" #include "d/d_tresure.h" #include "dusk/frame_interpolation.h" +#include "dusk/livesplit.h" #include "dusk/logging.h" #include "f_op/f_op_camera_mng.h" #include "f_op/f_op_draw_tag.h" @@ -23,6 +24,7 @@ #include "m_Do/m_Do_graphic.h" #include "m_Do/m_Do_main.h" #include "tracy/Tracy.hpp" +#include fapGm_HIO_c::fapGm_HIO_c() { mUsingHostIO = true; @@ -732,6 +734,64 @@ static void fapGm_AfterRecord() { dusk::frame_interp::end_record(); fapGm_After(); } + +static void duskExecute() { + 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() { @@ -747,27 +807,7 @@ void fapGm_Execute() { #endif #if TARGET_PC - 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 @@ -776,6 +816,9 @@ void fapGm_Execute() { fpcM_ManagementFunc(NULL, fapGm_After); #endif cCt_Counter(0); +#ifdef TARGET_PC + dusk::speedrun::onGameFrame(); +#endif } fapGm_HIO_c g_HIO; diff --git a/src/f_op/f_op_overlap_req.cpp b/src/f_op/f_op_overlap_req.cpp index 28d0a743cc..4d410fb2c2 100644 --- a/src/f_op/f_op_overlap_req.cpp +++ b/src/f_op/f_op_overlap_req.cpp @@ -7,6 +7,8 @@ #include "f_op/f_op_overlap_req.h" #include "f_pc/f_pc_manager.h" +#include "dusk/imgui/ImGuiMenuGame.hpp" + void fopOvlpReq_SetPeektime(overlap_request_class*, u16); static int fopOvlpReq_phase_Done(overlap_request_class* i_overlapReq) { @@ -16,6 +18,16 @@ static int fopOvlpReq_phase_Done(overlap_request_class* i_overlapReq) { i_overlapReq->peektime = 0; i_overlapReq->field_0x8 = 0; i_overlapReq->field_0xc = 0; + + #if TARGET_PC + if (dusk::getSettings().game.speedrunMode) { + if (dusk::m_speedrunInfo.m_isRunStarted) { + dusk::m_speedrunInfo.m_isPauseIGT = false; + dusk::m_speedrunInfo.m_totalLoadTime += OSGetTime() - dusk::m_speedrunInfo.m_loadStartTimestamp; + dusk::m_speedrunInfo.m_loadStartTimestamp = OSGetTime(); + } + } + #endif return cPhs_NEXT_e; } @@ -81,6 +93,14 @@ static int fopOvlpReq_phase_Create(overlap_request_class* i_overlapReq) { fpcLy_SetCurrentLayer(i_overlapReq->layer); i_overlapReq->request_id = fpcM_Create(i_overlapReq->procname, NULL, NULL); + +#if TARGET_PC + if (dusk::m_speedrunInfo.m_isRunStarted) { + dusk::m_speedrunInfo.m_isPauseIGT = true; + dusk::m_speedrunInfo.m_loadStartTimestamp = OSGetTime(); + } +#endif + return cPhs_NEXT_e; } diff --git a/src/f_op/f_op_scene_req.cpp b/src/f_op/f_op_scene_req.cpp index 79b87eee4c..abcb6ce848 100644 --- a/src/f_op/f_op_scene_req.cpp +++ b/src/f_op/f_op_scene_req.cpp @@ -4,13 +4,14 @@ */ #include "f_op/f_op_scene_req.h" +#include +#include "dusk/imgui/ImGuiConsole.hpp" +#include "dusk/logging.h" #include "f_op/f_op_overlap_mng.h" #include "f_op/f_op_scene.h" #include "f_op/f_op_scene_pause.h" #include "f_pc/f_pc_executor.h" #include "f_pc/f_pc_manager.h" -#include -#include "dusk/logging.h" static cPhs_Step fopScnRq_phase_ClearOverlap(scene_request_class* i_sceneReq) { return fopOvlpM_ClearOfReq() == 1 ? cPhs_NEXT_e : cPhs_INIT_e; 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..dbf962b173 100644 --- a/src/m_Do/m_Do_MemCard.cpp +++ b/src/m_Do/m_Do_MemCard.cpp @@ -12,6 +12,7 @@ #include "os_report.h" #include "dusk/os.h" #include "dusk/main.h" +#include "dusk/version.hpp" #if PLATFORM_WII || PLATFORM_SHIELD #include @@ -77,18 +78,25 @@ static OSThread MemCardThread; void mDoMemCd_Ctrl_c::ThdInit() { #if !PLATFORM_SHIELD - CARDInit(DUSK_GAME_NAME, DUSK_GAME_VERSION); + CARDSetLoadType((CARDFileType)dusk::getSettings().backend.cardFileType.getValue()); + + char version[5] = {}; + char maker[3] = {}; + std::memcpy(version, dusk::version::getDiskID().gameName, 4); + std::memcpy(maker, dusk::version::getDiskID().company, 2); + CARDInit(version, maker); #endif mCopyToPos = 0; 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_MemCardRWmng.cpp b/src/m_Do/m_Do_MemCardRWmng.cpp index 518bdd27e9..d7739d6158 100644 --- a/src/m_Do/m_Do_MemCardRWmng.cpp +++ b/src/m_Do/m_Do_MemCardRWmng.cpp @@ -11,6 +11,8 @@ #include #include +#include "dusk/version.hpp" + #if VERSION == VERSION_GCN_JPN #define HEADER_TITLE "ゼルダの伝説 トワイライトプリンセス" #define HEADER_COMMENT "%d月%d日のセーブデータです" @@ -313,12 +315,44 @@ s32 mDoMemCdRWm_StoreBannerNAND(NANDFileInfo* file) { #endif static void mDoMemCdRWm_BuildHeader(mDoMemCdRWm_HeaderData* header) { +#if !TARGET_PC snprintf(header->mTitle, sizeof(header->mTitle), HEADER_TITLE); +#endif OSCalendarTime time; OSTicksToCalendarTime(OSGetTime(), &time); -#if VERSION == VERSION_GCN_PAL +#if TARGET_PC + if (dusk::version::isRegionPal()) { + snprintf(header->mTitle, sizeof(header->mTitle), HEADER_TITLE); + + switch (dComIfGs_getPalLanguage()) { + case dSv_player_config_c::LANGUAGE_ENGLISH: + snprintf(header->mComment, sizeof(header->mComment), "%d/%d Save Data", time.mon + 1, time.mday); + break; + case dSv_player_config_c::LANGUAGE_GERMAN: + snprintf(header->mComment, sizeof(header->mComment), "%d/%d Spielstand", time.mday, time.mon + 1); + break; + case dSv_player_config_c::LANGUAGE_FRENCH: + snprintf(header->mComment, sizeof(header->mComment), "Donn%ces de jeu %d/%d", 0xE9, time.mday, time.mon + 1); + break; + case dSv_player_config_c::LANGUAGE_SPANISH: + snprintf(header->mComment, sizeof(header->mComment), "Datos guardados el %d/%d", time.mday, time.mon + 1); + break; + case dSv_player_config_c::LANGUAGE_ITALIAN: + snprintf(header->mComment, sizeof(header->mComment), "Dati salvati: %d/%d", time.mday, time.mon + 1); + break; + } + } else if (dusk::version::isRegionUsa()) { + snprintf(header->mTitle, sizeof(header->mTitle), HEADER_TITLE); + + snprintf(header->mComment, sizeof(header->mComment), HEADER_COMMENT, time.mon + 1, time.mday); + } else { + // TODO JPN SHIFT-JIS + // snprintf(header->mTitle, sizeof(header->mTitle), "ゼルダの伝説 トワイライトプリンセス"); + // snprintf(header->mComment, sizeof(header->mComment), "%d月%d日のセーブデータです", time.mon + 1, time.mday); + } +#elif VERSION == VERSION_GCN_PAL switch (dComIfGs_getPalLanguage()) { case dSv_player_config_c::LANGUAGE_ENGLISH: snprintf(header->mComment, sizeof(header->mComment), "%d/%d Save Data", time.mon + 1, time.mday); 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 d774a7de7d..425d1e25fe 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); } @@ -998,15 +1029,8 @@ static void drawDepth2(view_class* param_0, view_port_class* param_1, int param_ GX_FALSE, 0); } - #if TARGET_PC - // use full size for pc for higher quality background elements - u16 halfWidth = width; - u16 halfHeight = height; - #else u16 halfWidth = width >> 1; u16 halfHeight = height >> 1; - #endif - GXRenderModeObj* sp24 = JUTGetVideoManager()->getRenderMode(); GXSetCopyFilter(GX_FALSE, NULL, GX_TRUE, sp24->vfilter); GXSetTexCopySrc(x_orig, y_orig_pos, width, height); @@ -1124,6 +1148,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 +1303,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); @@ -1361,7 +1388,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; @@ -1394,8 +1421,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.0f, 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); @@ -1432,7 +1459,7 @@ 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); @@ -1466,7 +1493,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); @@ -1538,13 +1565,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); @@ -1659,7 +1681,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 @@ -1800,7 +1822,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 @@ -2029,8 +2051,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 @@ -2038,7 +2058,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(); @@ -2058,8 +2078,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); @@ -2105,13 +2124,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; @@ -2129,7 +2143,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 @@ -2165,7 +2179,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 @@ -2242,7 +2256,7 @@ int mDoGph_Painter() { #endif GX_DEBUG_GROUP(dComIfGd_drawOpaListPacket); - + #if DEBUG // "drawing up to special-use drawing (Opaque) except J3D (Rendering)" fapGm_HIO_c::stopCpuTimer("J3D以外などの特殊用(不透明)描画まで(レンダリング)"); @@ -2324,7 +2338,7 @@ int mDoGph_Painter() { GX_DEBUG_GROUP(dComIfGd_drawXluListInvisible); } } - + #if DEBUG // "drawing up to projection (Translucent)" @@ -2428,12 +2442,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; @@ -2530,12 +2540,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 @@ -2601,7 +2606,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(); @@ -2655,12 +2660,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); @@ -2693,18 +2693,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 715f9d3c84..d25debb38e 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,6 +50,7 @@ #include "dusk/crash_reporting.h" #include "dusk/dusk.h" #include "dusk/frame_interpolation.h" +#include "dusk/game_clock.h" #include "dusk/gyro.h" #include "dusk/imgui/ImGuiEngine.hpp" #include "dusk/logging.h" @@ -66,9 +66,15 @@ #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/version.hpp" +#include "dusk/discord_presence.hpp" +#include "tracy/Tracy.hpp" +#include "f_pc/f_pc_draw.h" #include "tracy/Tracy.hpp" // --- GLOBALS --- @@ -92,6 +98,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 +132,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 +204,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 +213,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 +238,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,26 +251,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(); - dusk::gyro::read(kSimStepSeconds); - fapGm_Execute(); - mDoAud_Execute(); - accumulator -= kSimStepSeconds; - } - dusk::frame_interp::interpolate(static_cast(accumulator / kSimStepSeconds)); - { - dusk::frame_interp::PresentationCameraScope presentation_camera; - cAPIGph_Painter(); + 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(); + } } + + 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(); - dusk::gyro::read(frame_seconds); + dusk::gyro::read(pacing.presentation_dt_seconds); // EXECUTE GAME LOGIC & RENDER // This calls mDoGph_Painter -> JFWDisplay -> GX Functions @@ -272,6 +296,11 @@ void main01(void) { aurora_end_frame(); FrameMark; + +#ifdef DUSK_DISCORD_RPC + dusk::discord::RunCallbacks(); + dusk::discord::UpdatePresence(); +#endif } while (dusk::IsRunning); exit:; @@ -354,7 +383,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()); @@ -363,13 +392,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; } @@ -388,10 +416,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; } @@ -438,6 +466,23 @@ static constexpr PADDefaultMapping defaultPadMapping = { static bool mainCalled = false; +static u8 selectedLanguage; + +u8 OSGetLanguage() { + return selectedLanguage; +} + +static void LanguageInit() { + // Keep language at 0 (English) if not on a PAL disk. + // Doubt this matters, but avoid funky shit. + if (!dusk::version::isRegionPal()) { + return; + } + + // Cache this to avoid funky shenanigans. + selectedLanguage = static_cast(dusk::getSettings().game.language.getValue()); +} + // ========================================================================= // PC ENTRY POINT // ========================================================================= @@ -482,47 +527,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); @@ -546,6 +597,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; } @@ -561,6 +615,9 @@ int game_main(int argc, char* argv[]) { } } + dusk::version::init(); + LanguageInit(); + OSInit(); mDoMain::sPowerOnTime = OSGetTime(); @@ -583,6 +640,8 @@ int game_main(int argc, char* argv[]) { main01(); + dusk::MoviePlayerShutdown(); + dusk::ShutdownCrashReporting(); dusk::ShutdownFileLogging(); fflush(stdout); @@ -593,6 +652,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}")