From a6690c2052552a7339664f7ec7965da1ac747ee3 Mon Sep 17 00:00:00 2001 From: madeline Date: Sun, 12 Apr 2026 23:22:25 -0700 Subject: [PATCH 01/15] speedrun timer --- CMakeLists.txt | 4 + files.cmake | 3 + include/dusk/livesplit.h | 16 ++ include/dusk/settings.h | 5 + src/d/d_bright_check.cpp | 4 + src/dusk/imgui/ImGuiConsole.cpp | 27 +++- src/dusk/imgui/ImGuiConsole.hpp | 4 + src/dusk/imgui/ImGuiMenuEnhancements.cpp | 18 +++ src/dusk/imgui/ImGuiMenuSpeedrunTimer.cpp | 96 ++++++++++++ src/dusk/imgui/ImGuiMenuSpeedrunTimer.hpp | 13 ++ src/dusk/livesplit.cpp | 183 ++++++++++++++++++++++ src/dusk/settings.cpp | 10 +- src/f_ap/f_ap_game.cpp | 4 + 13 files changed, 385 insertions(+), 2 deletions(-) create mode 100644 include/dusk/livesplit.h create mode 100644 src/dusk/imgui/ImGuiMenuSpeedrunTimer.cpp create mode 100644 src/dusk/imgui/ImGuiMenuSpeedrunTimer.hpp create mode 100644 src/dusk/livesplit.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 775606ca8e..f1b4e4e26c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -216,6 +216,10 @@ set(GAME_INCLUDE_DIRS 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) +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) diff --git a/files.cmake b/files.cmake index 7507fb6742..cfc6fcbdea 100644 --- a/files.cmake +++ b/files.cmake @@ -1361,6 +1361,8 @@ set(DUSK_FILES src/dusk/imgui/ImGuiMenuTools.hpp src/dusk/imgui/ImGuiMenuEnhancements.cpp src/dusk/imgui/ImGuiMenuEnhancements.hpp + src/dusk/imgui/ImGuiMenuSpeedrunTimer.cpp + src/dusk/imgui/ImGuiMenuSpeedrunTimer.hpp src/dusk/imgui/ImGuiPreLaunchWindow.cpp src/dusk/imgui/ImGuiPreLaunchWindow.hpp src/dusk/imgui/ImGuiFirstRunPreset.hpp @@ -1373,6 +1375,7 @@ set(DUSK_FILES src/dusk/imgui/ImGuiStubLog.cpp src/dusk/imgui/ImGuiMapLoader.cpp src/dusk/imgui/ImGuiSaveEditor.cpp + src/dusk/livesplit.cpp src/dusk/offset_ptr.cpp src/dusk/OSContext.cpp src/dusk/OSThread.cpp 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/settings.h b/include/dusk/settings.h index 2ab587d3ce..1a5772e310 100644 --- a/include/dusk/settings.h +++ b/include/dusk/settings.h @@ -79,6 +79,11 @@ struct UserSettings { // Controls ConfigVar enableTurboKeybind; + + // Tools + ConfigVar speedrunTimer; + ConfigVar speedrunTimerOverlay; + ConfigVar liveSplitEnabled; } game; struct { diff --git a/src/d/d_bright_check.cpp b/src/d/d_bright_check.cpp index ac28e46838..ae4849cbe9 100644 --- a/src/d/d_bright_check.cpp +++ b/src/d/d_bright_check.cpp @@ -9,6 +9,7 @@ #include "JSystem/J2DGraph/J2DScreen.h" #include "JSystem/J2DGraph/J2DTextBox.h" #include "d/d_msg_string.h" +#include "dusk/livesplit.h" #include "m_Do/m_Do_controller_pad.h" dBrightCheck_c::dBrightCheck_c(JKRArchive* i_archive) { @@ -138,6 +139,9 @@ 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(); +#endif mCompleteCheck = true; mMode = MODE_WAIT_e; } diff --git a/src/dusk/imgui/ImGuiConsole.cpp b/src/dusk/imgui/ImGuiConsole.cpp index 1bff5da458..dc27f17c39 100644 --- a/src/dusk/imgui/ImGuiConsole.cpp +++ b/src/dusk/imgui/ImGuiConsole.cpp @@ -16,6 +16,7 @@ #include "m_Do/m_Do_controller_pad.h" #include "m_Do/m_Do_main.h" #include "dusk/config.hpp" +#include "dusk/livesplit.h" #include "dusk/main.h" #include "dusk/settings.h" #include "dusk/audio/DuskAudioSystem.h" @@ -38,6 +39,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(text.data(), text.data() + text.size()).x; @@ -235,6 +240,8 @@ namespace dusk { m_menuEnhancements.draw(); m_menuTools.draw(); + m_menuSpeedrunTimer.draw(); + ImGui::SetCursorPosX(ImGui::GetWindowWidth() - 80.0f * ImGuiScale()); ImGuiIO& io = ImGui::GetIO(); ImGuiStringViewText(fmt::format(FMT_STRING("FPS: {:.2f}\n"), io.Framerate)); @@ -252,12 +259,26 @@ namespace dusk { } if (dusk::IsGameLaunched && !m_isLaunchInitialized) { - m_toasts.emplace_back("Press F1 to toggle menu"s, 5.f); + DuskToast("Press F1 to toggle menu"s); m_isLaunchInitialized = true; + if (getSettings().game.liveSplitEnabled) { + dusk::speedrun::connectLiveSplit(); + } } + m_menuSpeedrunTimer.drawOverlay(); + m_menuGame.windowControllerConfig(); m_menuGame.windowInputViewer(); + + 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) { m_menuTools.ShowDebugOverlay(); m_menuTools.ShowCameraOverlay(); @@ -383,6 +404,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..bec998eb63 100644 --- a/src/dusk/imgui/ImGuiConsole.hpp +++ b/src/dusk/imgui/ImGuiConsole.hpp @@ -9,6 +9,7 @@ #include "ImGuiFirstRunPreset.hpp" #include "ImGuiMenuEnhancements.hpp" #include "ImGuiMenuGame.hpp" +#include "ImGuiMenuSpeedrunTimer.hpp" #include "ImGuiMenuTools.hpp" #include "ImGuiPreLaunchWindow.hpp" #include "imgui.h" @@ -22,6 +23,7 @@ public: void PostDraw(); static bool CheckMenuViewToggle(ImGuiKey key, bool& active); + void AddToast(std::string_view message, float duration = 3.f); private: struct Toast { @@ -39,6 +41,7 @@ private: ImGuiFirstRunPreset m_firstRunPreset; ImGuiMenuGame m_menuGame; ImGuiMenuEnhancements m_menuEnhancements; + ImGuiMenuSpeedrunTimer m_menuSpeedrunTimer; ImGuiPreLaunchWindow m_preLaunchWindow; // Keep always last @@ -57,6 +60,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/ImGuiMenuEnhancements.cpp b/src/dusk/imgui/ImGuiMenuEnhancements.cpp index 50e44f5c60..dfe8f5282b 100644 --- a/src/dusk/imgui/ImGuiMenuEnhancements.cpp +++ b/src/dusk/imgui/ImGuiMenuEnhancements.cpp @@ -2,6 +2,8 @@ #include "ImGuiMenuEnhancements.hpp" #include "ImGuiConfig.hpp" +#include "ImGuiConsole.hpp" +#include "dusk/livesplit.h" #include "dusk/settings.h" namespace dusk { @@ -181,6 +183,22 @@ namespace dusk { "This will not work with the \"Unlock Framerate\" enhancement."); } + bool prevSpeedrunTimer = getSettings().game.speedrunTimer; + config::ImGuiCheckbox("Speedrun Timer", getSettings().game.speedrunTimer); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Shows a speedrun timer in the menu bar."); + } + if ((bool)getSettings().game.speedrunTimer != prevSpeedrunTimer) { + if (!getSettings().game.speedrunTimer) { + getSettings().game.speedrunTimerOverlay.setValue(false); + if (getSettings().game.liveSplitEnabled) { + getSettings().game.liveSplitEnabled.setValue(false); + dusk::speedrun::disconnectLiveSplit(); + DuskToast("LiveSplit disconnected", 3.f); + } + } + } + ImGui::EndMenu(); } diff --git a/src/dusk/imgui/ImGuiMenuSpeedrunTimer.cpp b/src/dusk/imgui/ImGuiMenuSpeedrunTimer.cpp new file mode 100644 index 0000000000..6edbb7e084 --- /dev/null +++ b/src/dusk/imgui/ImGuiMenuSpeedrunTimer.cpp @@ -0,0 +1,96 @@ +#include "fmt/format.h" +#include "imgui.h" + +#include "ImGuiMenuSpeedrunTimer.hpp" +#include "ImGuiConfig.hpp" +#include "ImGuiConsole.hpp" +#include "dusk/livesplit.h" +#include "dusk/settings.h" + +namespace dusk { + static auto formatTime(uint64_t frames) { + const uint64_t totalSec = frames / 30; + + return fmt::format( + FMT_STRING("{:d}:{:02d}:{:02d}.{:02d}"), + totalSec / 3600, + (totalSec / 60) % 60, + totalSec % 60, + (int)(((f32)(frames % 30) / 30.0f) * 100.0f) + ); + } + + void ImGuiMenuSpeedrunTimer::draw() { + if (!getSettings().game.speedrunTimer) return; + + const uint64_t frames = dusk::speedrun::getFrameCount(); + + if (ImGui::BeginMenu("Timer##speedrun_timer")) { + ImGui::TextUnformatted(formatTime(frames).c_str()); + + ImGui::Separator(); + + if (ImGui::MenuItem("Reset")) { + dusk::speedrun::reset(); + } + + config::ImGuiCheckbox("Show Overlay", getSettings().game.speedrunTimerOverlay); + + bool prevLiveSplit = getSettings().game.liveSplitEnabled; + config::ImGuiCheckbox("LiveSplit Connection", getSettings().game.liveSplitEnabled); + + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Connect to LiveSplit server on localhost:16834."); + } + + 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 ImGuiMenuSpeedrunTimer::drawOverlay() { + if (!getSettings().game.speedrunTimer || !getSettings().game.speedrunTimerOverlay) { + return; + } + + const uint64_t frames = dusk::speedrun::getFrameCount(); + const ImGuiViewport* viewport = ImGui::GetMainViewport(); + + const float padding = 10.f; + + ImGui::SetNextWindowPos( + ImVec2( + viewport->WorkPos.x + viewport->WorkSize.x - padding, + viewport->WorkPos.y + viewport->WorkSize.y - padding + ), + ImGuiCond_Always, ImVec2(1.f, 1.f) + ); + + ImGui::SetNextWindowBgAlpha(0.65f); + + const float fixedWidth = ImGui::CalcTextSize("9:59:59.99").x; + + ImGui::SetNextWindowContentSize(ImVec2(fixedWidth, 0.f)); + + if ( + ImGui::Begin("##speedrun_overlay", nullptr, + ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoInputs | + ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoMove | + ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoFocusOnAppearing | + ImGuiWindowFlags_NoNav + ) + ) { + ImGui::TextUnformatted(formatTime(frames).c_str()); + } + ImGui::End(); + } +} diff --git a/src/dusk/imgui/ImGuiMenuSpeedrunTimer.hpp b/src/dusk/imgui/ImGuiMenuSpeedrunTimer.hpp new file mode 100644 index 0000000000..8cb524469f --- /dev/null +++ b/src/dusk/imgui/ImGuiMenuSpeedrunTimer.hpp @@ -0,0 +1,13 @@ +#ifndef DUSK_IMGUI_MENUSPEEDRUNTIMER_HPP +#define DUSK_IMGUI_MENUSPEEDRUNTIMER_HPP + +namespace dusk { + class ImGuiMenuSpeedrunTimer { + public: + void draw(); + void drawOverlay(); + private: + }; +} + +#endif // DUSK_IMGUI_MENUSPEEDRUNTIMER_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/settings.cpp b/src/dusk/settings.cpp index 0e39e55854..064bd8a86d 100644 --- a/src/dusk/settings.cpp +++ b/src/dusk/settings.cpp @@ -66,7 +66,12 @@ UserSettings g_userSettings = { .restoreWiiGlitches {"game.restoreWiiGlitches", false}, // Controls - .enableTurboKeybind {"game.enableTurboKeybind", false} + .enableTurboKeybind {"game.enableTurboKeybind", false}, + + // Tools + .speedrunTimer {"game.speedrunTimer", false}, + .speedrunTimerOverlay {"game.speedrunTimerOverlay", false}, + .liveSplitEnabled {"game.liveSplitEnabled", false} }, .backend = { @@ -123,6 +128,9 @@ void registerSettings() { Register(g_userSettings.game.noLowHpSound); Register(g_userSettings.game.midnasLamentNonStop); Register(g_userSettings.game.enableTurboKeybind); + Register(g_userSettings.game.speedrunTimer); + Register(g_userSettings.game.speedrunTimerOverlay); + Register(g_userSettings.game.liveSplitEnabled); Register(g_userSettings.game.fastSpinner); Register(g_userSettings.game.enableFrameInterpolation); Register(g_userSettings.game.enableGyroAim); diff --git a/src/f_ap/f_ap_game.cpp b/src/f_ap/f_ap_game.cpp index f54ca1edad..48cf4fa16d 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" @@ -771,6 +772,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; From 8e121a7e5187fb1f30b13b2d520c4303e2cd81db Mon Sep 17 00:00:00 2001 From: TakaRikka Date: Sat, 25 Apr 2026 21:12:51 -0700 Subject: [PATCH 02/15] update timer impl --- files.cmake | 1 + include/dusk/settings.h | 3 +- src/d/actor/d_a_alink_demo.inc | 10 ++ src/d/d_bright_check.cpp | 8 ++ src/d/d_s_name.cpp | 20 +++- src/dusk/imgui/ImGuiConsole.cpp | 9 +- src/dusk/imgui/ImGuiConsole.hpp | 7 +- src/dusk/imgui/ImGuiMenuGame.cpp | 121 +++++++++++++++++++++- src/dusk/imgui/ImGuiMenuGame.hpp | 22 ++++ src/dusk/imgui/ImGuiMenuSpeedrunTimer.cpp | 96 ----------------- src/dusk/imgui/ImGuiMenuTools.cpp | 9 ++ src/dusk/settings.cpp | 6 +- src/f_op/f_op_scene_req.cpp | 21 +++- 13 files changed, 215 insertions(+), 118 deletions(-) delete mode 100644 src/dusk/imgui/ImGuiMenuSpeedrunTimer.cpp diff --git a/files.cmake b/files.cmake index af101b6c47..91b0c0fab7 100644 --- a/files.cmake +++ b/files.cmake @@ -1461,6 +1461,7 @@ set(DUSK_FILES src/dusk/imgui/ImGuiStateShare.hpp src/dusk/imgui/ImGuiStateShare.cpp src/dusk/iso_validate.cpp + src/dusk/livesplit.cpp src/dusk/offset_ptr.cpp src/dusk/OSContext.cpp src/dusk/OSThread.cpp diff --git a/include/dusk/settings.h b/include/dusk/settings.h index 522e172e7c..7fb8c85a51 100644 --- a/include/dusk/settings.h +++ b/include/dusk/settings.h @@ -119,8 +119,7 @@ struct UserSettings { ConfigVar enableTurboKeybind; // Tools - ConfigVar speedrunTimer; - ConfigVar speedrunTimerOverlay; + ConfigVar speedrunMode; ConfigVar liveSplitEnabled; } game; diff --git a/src/d/actor/d_a_alink_demo.inc b/src/d/actor/d_a_alink_demo.inc index aa77b24129..b46af5cacb 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::g_imguiConsole.isSpeedrunStart()) { + dusk::g_imguiConsole.stopSpeedrun(); + } + } +#endif + return 1; } diff --git a/src/d/d_bright_check.cpp b/src/d/d_bright_check.cpp index 2c683eb819..88d0b7a2d8 100644 --- a/src/d/d_bright_check.cpp +++ b/src/d/d_bright_check.cpp @@ -10,6 +10,7 @@ #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) { @@ -141,6 +142,13 @@ void dBrightCheck_c::modeMove() { 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::g_imguiConsole.isSpeedrunStart()) { + dusk::g_imguiConsole.startSpeedrun(); + } + } #endif mCompleteCheck = true; mMode = MODE_WAIT_e; diff --git a/src/d/d_s_name.cpp b/src/d/d_s_name.cpp index 735ce4d0ca..9a20ca6caa 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,15 @@ 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::g_imguiConsole.isSpeedrunStart()) { + dusk::g_imguiConsole.startSpeedrun(); + } + } +#endif } } diff --git a/src/dusk/imgui/ImGuiConsole.cpp b/src/dusk/imgui/ImGuiConsole.cpp index c64314a806..955cef3920 100644 --- a/src/dusk/imgui/ImGuiConsole.cpp +++ b/src/dusk/imgui/ImGuiConsole.cpp @@ -334,7 +334,6 @@ namespace dusk { if (showMenu && ImGui::BeginMainMenuBar()) { m_menuGame.draw(); m_menuTools.draw(); - m_menuSpeedrunTimer.draw(); const auto fpsLabel = fmt::format(FMT_STRING("FPS: {:.2f}\n"), ImGui::GetIO().Framerate); @@ -366,12 +365,11 @@ namespace dusk { } } - m_menuSpeedrunTimer.drawOverlay(); - UpdateDragScroll(); m_menuGame.windowControllerConfig(); m_menuGame.windowInputViewer(); + m_menuGame.drawSpeedrunTimerOverlay(); if (getSettings().game.liveSplitEnabled) { dusk::speedrun::updateLiveSplit(); @@ -381,7 +379,7 @@ namespace dusk { AddToast("LiveSplit disconnected"); } - if (dusk::IsGameLaunched) { + if (dusk::IsGameLaunched && !dusk::getSettings().game.speedrunMode) { m_menuTools.ShowDebugOverlay(); m_menuTools.ShowCameraOverlay(); m_menuTools.ShowProcessManager(); @@ -392,8 +390,9 @@ namespace dusk { m_menuTools.ShowPlayerInfo(); m_menuTools.ShowAudioDebug(); m_menuTools.ShowSaveEditor(); + m_menuTools.ShowStateShare(); } - m_menuTools.ShowStateShare(); + DuskDebugPad(); // temporary, remove later // Hide mouse cursor if the F1 menu is not open and the cursor is idle for 3 seconds. diff --git a/src/dusk/imgui/ImGuiConsole.hpp b/src/dusk/imgui/ImGuiConsole.hpp index 85e65d21a1..5e9f2aa523 100644 --- a/src/dusk/imgui/ImGuiConsole.hpp +++ b/src/dusk/imgui/ImGuiConsole.hpp @@ -10,7 +10,6 @@ #include "ImGuiFirstRunPreset.hpp" #include "ImGuiMenuGame.hpp" -#include "ImGuiMenuSpeedrunTimer.hpp" #include "ImGuiMenuTools.hpp" #include "ImGuiPreLaunchWindow.hpp" #include "imgui.h" @@ -30,6 +29,11 @@ public: static bool CheckMenuViewToggle(ImGuiKey key, bool& active); void AddToast(std::string_view message, float duration = 3.f); + bool isSpeedrunStart() const { return m_menuGame.isRunStarted(); } + void startSpeedrun() { m_menuGame.startRun(); } + void stopSpeedrun() { m_menuGame.stopRun(); } + void incSpeedrunTotalLoadTime(OSTime time) { m_menuGame.incTotalLoadTime(time); } + private: struct Toast { std::string message; @@ -53,7 +57,6 @@ private: ImGuiFirstRunPreset m_firstRunPreset; ImGuiMenuGame m_menuGame; - ImGuiMenuSpeedrunTimer m_menuSpeedrunTimer; ImGuiPreLaunchWindow m_preLaunchWindow; // Keep always last diff --git a/src/dusk/imgui/ImGuiMenuGame.cpp b/src/dusk/imgui/ImGuiMenuGame.cpp index 7e54f2ffa3..fbea49bcf9 100644 --- a/src/dusk/imgui/ImGuiMenuGame.cpp +++ b/src/dusk/imgui/ImGuiMenuGame.cpp @@ -12,12 +12,15 @@ #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 "m_Do/m_Do_main.h" + namespace { constexpr int kInternalResolutionScaleMax = 12; } // namespace @@ -199,6 +202,7 @@ namespace dusk { 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); @@ -211,6 +215,7 @@ namespace dusk { ImGui::SetTooltip("Hearts will never drop from enemies,\n" "pots and various other places."); } + ImGui::EndDisabled(); ImGui::SeparatorText("Quality of Life"); @@ -280,12 +285,39 @@ namespace dusk { 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); @@ -293,8 +325,8 @@ namespace dusk { config::ImGuiCheckbox("Infinite Oil", getSettings().game.infiniteOil); config::ImGuiCheckbox("Infinite Oxygen", getSettings().game.infiniteOxygen); config::ImGuiCheckbox("Infinite Rupees", getSettings().game.infiniteRupees); - config::ImGuiCheckbox("Items Don't Despawn", getSettings().game.enableIndefiniteItemDrops); - ImGui::SetItemTooltip("Items Don't Despawn Unless You Load A Different Room In Which Case They Do But Even Under Some Circumstances They Don't, It Is Quite Rare Though"); + 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); @@ -317,6 +349,8 @@ namespace dusk { ImGui::SetTooltip("Makes the magic armor work without rupees."); } + ImGui::EndDisabled(); + ImGui::EndMenu(); } } @@ -431,10 +465,12 @@ namespace dusk { 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); @@ -971,4 +1007,85 @@ namespace dusk { 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); + } + + 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.m_endTimestamp = 0; + m_speedrunInfo.m_startTimestamp = 0; + m_speedrunInfo.m_totalLoadTime = 0; + m_speedrunInfo.m_isRunStarted = false; + } + + // 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)); + + ImGui::Text("IGT"); + ImGui::SameLine(60.0f); + ImGuiStringViewText(GetFormattedTime(elapsedTime - m_speedrunInfo.m_totalLoadTime)); + } + ImGui::End(); + } } diff --git a/src/dusk/imgui/ImGuiMenuGame.hpp b/src/dusk/imgui/ImGuiMenuGame.hpp index e21374c8f4..795cf5f446 100644 --- a/src/dusk/imgui/ImGuiMenuGame.hpp +++ b/src/dusk/imgui/ImGuiMenuGame.hpp @@ -15,9 +15,23 @@ namespace dusk { void windowInputViewer(); void windowControllerConfig(); + void drawSpeedrunTimerOverlay(); static void ToggleFullscreen(); + static void resetForSpeedrunMode(); + bool isRunStarted() const { return m_speedrunInfo.m_isRunStarted; } + void startRun() { + resetForSpeedrunMode(); + m_speedrunInfo.m_isRunStarted = true; + m_speedrunInfo.m_startTimestamp = OSGetTime(); + } + void stopRun() { + m_speedrunInfo.m_isRunStarted = false; + m_speedrunInfo.m_endTimestamp = OSGetTime() - m_speedrunInfo.m_startTimestamp; + } + void incTotalLoadTime(OSTime time) { m_speedrunInfo.m_totalLoadTime += time; } + private: void drawAudioMenu(); void drawInputMenu(); @@ -40,6 +54,14 @@ namespace dusk { bool m_showInputViewerGyro = false; int m_inputOverlayCorner = 3; std::string m_controllerName; + + struct { + bool m_showTimerWindow = false; + bool m_isRunStarted = false; + OSTime m_startTimestamp = 0; + OSTime m_endTimestamp = 0; + OSTime m_totalLoadTime = 0; + } m_speedrunInfo; }; } diff --git a/src/dusk/imgui/ImGuiMenuSpeedrunTimer.cpp b/src/dusk/imgui/ImGuiMenuSpeedrunTimer.cpp deleted file mode 100644 index 6edbb7e084..0000000000 --- a/src/dusk/imgui/ImGuiMenuSpeedrunTimer.cpp +++ /dev/null @@ -1,96 +0,0 @@ -#include "fmt/format.h" -#include "imgui.h" - -#include "ImGuiMenuSpeedrunTimer.hpp" -#include "ImGuiConfig.hpp" -#include "ImGuiConsole.hpp" -#include "dusk/livesplit.h" -#include "dusk/settings.h" - -namespace dusk { - static auto formatTime(uint64_t frames) { - const uint64_t totalSec = frames / 30; - - return fmt::format( - FMT_STRING("{:d}:{:02d}:{:02d}.{:02d}"), - totalSec / 3600, - (totalSec / 60) % 60, - totalSec % 60, - (int)(((f32)(frames % 30) / 30.0f) * 100.0f) - ); - } - - void ImGuiMenuSpeedrunTimer::draw() { - if (!getSettings().game.speedrunTimer) return; - - const uint64_t frames = dusk::speedrun::getFrameCount(); - - if (ImGui::BeginMenu("Timer##speedrun_timer")) { - ImGui::TextUnformatted(formatTime(frames).c_str()); - - ImGui::Separator(); - - if (ImGui::MenuItem("Reset")) { - dusk::speedrun::reset(); - } - - config::ImGuiCheckbox("Show Overlay", getSettings().game.speedrunTimerOverlay); - - bool prevLiveSplit = getSettings().game.liveSplitEnabled; - config::ImGuiCheckbox("LiveSplit Connection", getSettings().game.liveSplitEnabled); - - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("Connect to LiveSplit server on localhost:16834."); - } - - 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 ImGuiMenuSpeedrunTimer::drawOverlay() { - if (!getSettings().game.speedrunTimer || !getSettings().game.speedrunTimerOverlay) { - return; - } - - const uint64_t frames = dusk::speedrun::getFrameCount(); - const ImGuiViewport* viewport = ImGui::GetMainViewport(); - - const float padding = 10.f; - - ImGui::SetNextWindowPos( - ImVec2( - viewport->WorkPos.x + viewport->WorkSize.x - padding, - viewport->WorkPos.y + viewport->WorkSize.y - padding - ), - ImGuiCond_Always, ImVec2(1.f, 1.f) - ); - - ImGui::SetNextWindowBgAlpha(0.65f); - - const float fixedWidth = ImGui::CalcTextSize("9:59:59.99").x; - - ImGui::SetNextWindowContentSize(ImVec2(fixedWidth, 0.f)); - - if ( - ImGui::Begin("##speedrun_overlay", nullptr, - ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoInputs | - ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoMove | - ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoFocusOnAppearing | - ImGuiWindowFlags_NoNav - ) - ) { - ImGui::TextUnformatted(formatTime(frames).c_str()); - } - ImGui::End(); - } -} diff --git a/src/dusk/imgui/ImGuiMenuTools.cpp b/src/dusk/imgui/ImGuiMenuTools.cpp index 00a03e635b..5dd5a62d84 100644 --- a/src/dusk/imgui/ImGuiMenuTools.cpp +++ b/src/dusk/imgui/ImGuiMenuTools.cpp @@ -50,10 +50,14 @@ namespace dusk { 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(); } @@ -69,6 +73,8 @@ namespace dusk { } 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; @@ -117,6 +123,9 @@ namespace dusk { } ImGui::MenuItem("OSReport Force", nullptr, &OSReportReallyForceEnable); + + ImGui::EndDisabled(); + ImGui::EndMenu(); } } diff --git a/src/dusk/settings.cpp b/src/dusk/settings.cpp index bddc30cb84..843dd4b133 100644 --- a/src/dusk/settings.cpp +++ b/src/dusk/settings.cpp @@ -92,8 +92,7 @@ UserSettings g_userSettings = { .enableTurboKeybind {"game.enableTurboKeybind", false}, // Tools - .speedrunTimer {"game.speedrunTimer", false}, - .speedrunTimerOverlay {"game.speedrunTimerOverlay", false}, + .speedrunMode {"game.speedrunMode", false}, .liveSplitEnabled {"game.liveSplitEnabled", false} }, @@ -162,8 +161,7 @@ void registerSettings() { Register(g_userSettings.game.noLowHpSound); Register(g_userSettings.game.midnasLamentNonStop); Register(g_userSettings.game.enableTurboKeybind); - Register(g_userSettings.game.speedrunTimer); - Register(g_userSettings.game.speedrunTimerOverlay); + Register(g_userSettings.game.speedrunMode); Register(g_userSettings.game.liveSplitEnabled); Register(g_userSettings.game.fastSpinner); Register(g_userSettings.game.infiniteHearts); diff --git a/src/f_op/f_op_scene_req.cpp b/src/f_op/f_op_scene_req.cpp index 79b87eee4c..e4ce889266 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; @@ -39,6 +40,10 @@ static cPhs_Step fopScnRq_phase_IsDoneOverlap(scene_request_class* i_sceneReq) { static BOOL l_fopScnRq_IsUsingOfOverlap; +#if TARGET_PC +static OSTime l_fopScnRq_StartTime = 0; +#endif + static cPhs_Step fopScnRq_phase_Done(scene_request_class* i_sceneReq) { if (i_sceneReq->create_request.parameters != 1) { @@ -48,6 +53,14 @@ static cPhs_Step fopScnRq_phase_Done(scene_request_class* i_sceneReq) { } l_fopScnRq_IsUsingOfOverlap = FALSE; + #if TARGET_PC + if (dusk::getSettings().game.speedrunMode) { + if (dusk::g_imguiConsole.isSpeedrunStart()) { + dusk::g_imguiConsole.incSpeedrunTotalLoadTime(OSGetTime() - l_fopScnRq_StartTime); + } + } + #endif + return cPhs_NEXT_e; } @@ -88,6 +101,10 @@ static scene_request_class* fopScnRq_FadeRequest(s16 i_procname, u16 i_peektime) req = fopOvlpM_Request(i_procname, i_peektime); if (req != NULL) { l_fopScnRq_IsUsingOfOverlap = TRUE; + + #if TARGET_PC + l_fopScnRq_StartTime = OSGetTime(); + #endif } } From 6cf94b4491c62684e47add925d8c4f70d39dbf90 Mon Sep 17 00:00:00 2001 From: TakaRikka Date: Sat, 25 Apr 2026 21:18:51 -0700 Subject: [PATCH 03/15] remove unneeded file --- src/dusk/imgui/ImGuiMenuSpeedrunTimer.hpp | 13 ------------- 1 file changed, 13 deletions(-) delete mode 100644 src/dusk/imgui/ImGuiMenuSpeedrunTimer.hpp diff --git a/src/dusk/imgui/ImGuiMenuSpeedrunTimer.hpp b/src/dusk/imgui/ImGuiMenuSpeedrunTimer.hpp deleted file mode 100644 index 8cb524469f..0000000000 --- a/src/dusk/imgui/ImGuiMenuSpeedrunTimer.hpp +++ /dev/null @@ -1,13 +0,0 @@ -#ifndef DUSK_IMGUI_MENUSPEEDRUNTIMER_HPP -#define DUSK_IMGUI_MENUSPEEDRUNTIMER_HPP - -namespace dusk { - class ImGuiMenuSpeedrunTimer { - public: - void draw(); - void drawOverlay(); - private: - }; -} - -#endif // DUSK_IMGUI_MENUSPEEDRUNTIMER_HPP From 3498ded9d9c2ae29b032915cdd8122577683b89f Mon Sep 17 00:00:00 2001 From: MelonSpeedruns Date: Sun, 26 Apr 2026 10:18:47 -0400 Subject: [PATCH 04/15] ImGui Button name change + Fix Freecam Invert X Axis option --- src/d/d_camera.cpp | 7 +++---- src/dusk/imgui/ImGuiPreLaunchWindow.cpp | 2 +- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/d/d_camera.cpp b/src/d/d_camera.cpp index 917b30709a..8efef81b8e 100644 --- a/src/d/d_camera.cpp +++ b/src/d/d_camera.cpp @@ -7493,10 +7493,9 @@ bool dCamera_c::freeCamera() { } camMovement = camMovement.normalize(); - camMovement.x *= (dusk::getSettings().game.invertCameraXAxis ? 1.0f : -1.0f) * dusk::getSettings().game.freeCameraSensitivity * 4.0f; - camMovement.y *= (dusk::getSettings().game.invertCameraYAxis ? 1.0f : -1.0f) * dusk::getSettings().game.freeCameraSensitivity * 4.0f; - mCamParam.freeXAngle += camMovement.x * magnitude * dusk::getSettings().game.freeCameraSensitivity; - mCamParam.freeYAngle += camMovement.y * magnitude * dusk::getSettings().game.freeCameraSensitivity; + 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) { diff --git a/src/dusk/imgui/ImGuiPreLaunchWindow.cpp b/src/dusk/imgui/ImGuiPreLaunchWindow.cpp index 6347e9bc01..4bfcce9047 100644 --- a/src/dusk/imgui/ImGuiPreLaunchWindow.cpp +++ b/src/dusk/imgui/ImGuiPreLaunchWindow.cpp @@ -193,7 +193,7 @@ void ImGuiPreLaunchWindow::drawOptions() { ImGui::InputText("Game ISO Path", &m_selectedIsoPath, ImGuiInputTextFlags_ReadOnly); ImGui::SameLine(); - if (ImGui::Button("Set")) { + if (ImGui::Button(m_selectedIsoPath == "" ? "Set" : "Change")) { ShowFileSelect(&fileDialogCallback, this, aurora::window::get_sdl_window(), skGameDiscFileFilters.data(), int(skGameDiscFileFilters.size()), nullptr, false); From 3859f397294e6e906fdf372ed64edcbcf38a623f Mon Sep 17 00:00:00 2001 From: MelonSpeedruns Date: Sun, 26 Apr 2026 10:25:10 -0400 Subject: [PATCH 05/15] Fix camera radius to be smoother --- src/d/d_camera.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/d/d_camera.cpp b/src/d/d_camera.cpp index 8efef81b8e..c59cf1c525 100644 --- a/src/d/d_camera.cpp +++ b/src/d/d_camera.cpp @@ -7502,7 +7502,7 @@ bool dCamera_c::freeCamera() { 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 * 15.0f, 300.0f, 10000.0f); + mViewCache.mDirection.mRadius = std::clamp((mCamParam.freeYAngle + 35.0f) * 10.0f, 300.0f, 10000.0f); } return mCamParam.mManualMode; From 5c84978c3cfd6a1d15228198d6537a00a8961f99 Mon Sep 17 00:00:00 2001 From: "Jasper St. Pierre" Date: Sun, 26 Apr 2026 10:44:52 -0700 Subject: [PATCH 06/15] evil perf fix closes #552 --- src/d/d_kankyo_rain.cpp | 31 ++++++++++++++++++++++++++++--- 1 file changed, 28 insertions(+), 3 deletions(-) diff --git a/src/d/d_kankyo_rain.cpp b/src/d/d_kankyo_rain.cpp index 822612768c..09e230c9fd 100644 --- a/src/d/d_kankyo_rain.cpp +++ b/src/d/d_kankyo_rain.cpp @@ -5962,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); @@ -6162,6 +6164,8 @@ static void dKyr_evil_draw2(Mtx drawMtx, u8** tex) { } } + IF_DUSK(GXPopDebugGroup()); + GXSetClipMode(GX_CLIP_ENABLE); J3DShape::resetVcdVatCache(); } @@ -6199,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); @@ -6231,8 +6237,8 @@ void dKyr_evil_draw(Mtx drawMtx, u8** tex) { 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); @@ -6255,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++) { @@ -6373,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; @@ -6412,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(); } @@ -6425,6 +6448,8 @@ void dKyr_evil_draw(Mtx drawMtx, u8** tex) { } } + IF_DUSK(GXPopDebugGroup()); + J3DShape::resetVcdVatCache(); GXSetClipMode(GX_CLIP_ENABLE); From 50e2d9d1a713afa52cb5fcd533e31a21527c7e8e Mon Sep 17 00:00:00 2001 From: "Jasper St. Pierre" Date: Sun, 26 Apr 2026 11:18:23 -0700 Subject: [PATCH 07/15] grass draw fix closes #534 --- src/d/actor/d_flower.inc | 14 ++------------ src/d/actor/d_grass.inc | 14 ++++++-------- 2 files changed, 8 insertions(+), 20 deletions(-) diff --git a/src/d/actor/d_flower.inc b/src/d/actor/d_flower.inc index 5f54ea4bda..4024d5ad6a 100644 --- a/src/d/actor/d_flower.inc +++ b/src/d/actor/d_flower.inc @@ -7,13 +7,8 @@ #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" @@ -136,13 +131,8 @@ 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 using GameVersion = dusk::version::GameVersion; @@ -592,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 diff --git a/src/d/actor/d_grass.inc b/src/d/actor/d_grass.inc index 9bc8815753..3d67a57007 100644 --- a/src/d/actor/d_grass.inc +++ b/src/d/actor/d_grass.inc @@ -494,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 @@ -646,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); } @@ -683,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); } From e75ea18ef01c2be02dc620d691c3eb13ce9105eb Mon Sep 17 00:00:00 2001 From: "Jasper St. Pierre" Date: Sun, 26 Apr 2026 11:52:20 -0700 Subject: [PATCH 08/15] map deco perf --- include/dusk/settings.h | 1 + src/d/d_map.cpp | 11 ++++------- src/d/d_map_path.cpp | 34 ++++++++++++++++++++++++-------- src/dusk/imgui/ImGuiMenuGame.cpp | 2 ++ src/dusk/settings.cpp | 2 ++ 5 files changed, 35 insertions(+), 15 deletions(-) diff --git a/include/dusk/settings.h b/include/dusk/settings.h index a7815569d0..875b839d44 100644 --- a/include/dusk/settings.h +++ b/include/dusk/settings.h @@ -96,6 +96,7 @@ struct UserSettings { ConfigVar internalResolutionScale; ConfigVar shadowResolutionMultiplier; ConfigVar enableDepthOfField; + ConfigVar enableMapBackground; // Audio ConfigVar noLowHpSound; diff --git a/src/d/d_map.cpp b/src/d/d_map.cpp index 7dc5187a88..a5163091fd 100644 --- a/src/d/d_map.cpp +++ b/src/d/d_map.cpp @@ -539,17 +539,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 || diff --git a/src/d/d_map_path.cpp b/src/d/d_map_path.cpp index 8288a2ef9f..f94cdc9475 100644 --- a/src/d/d_map_path.cpp +++ b/src/d/d_map_path.cpp @@ -497,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) { @@ -527,8 +521,32 @@ void dRenderingFDAmap_c::renderingDecoration(dDrawPath_c::line_class const* p_li 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, @@ -547,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]); @@ -555,6 +572,7 @@ void dRenderingFDAmap_c::renderingDecoration(dDrawPath_c::line_class const* p_li GXEnd(); data_p++; } +#endif setTevSettingNonTextureDirectColor(); GXClearVtxDesc(); diff --git a/src/dusk/imgui/ImGuiMenuGame.cpp b/src/dusk/imgui/ImGuiMenuGame.cpp index dcfcc55657..10b6e65bf2 100644 --- a/src/dusk/imgui/ImGuiMenuGame.cpp +++ b/src/dusk/imgui/ImGuiMenuGame.cpp @@ -167,6 +167,8 @@ namespace dusk { config::ImGuiCheckbox("Enable Depth of Field", getSettings().game.enableDepthOfField); + config::ImGuiCheckbox("Enable Mini-Map Shadows", getSettings().game.enableMapBackground); + ImGui::EndMenu(); } } diff --git a/src/dusk/settings.cpp b/src/dusk/settings.cpp index eb1d513ba9..84e56eda4f 100644 --- a/src/dusk/settings.cpp +++ b/src/dusk/settings.cpp @@ -55,6 +55,7 @@ UserSettings g_userSettings = { .internalResolutionScale {"game.internalResolutionScale", 0}, .shadowResolutionMultiplier {"game.shadowResolutionMultiplier", 1}, .enableDepthOfField {"game.enableDepthOfField", true}, + .enableMapBackground {"game.enableMapBackground", true}, // Audio .noLowHpSound {"game.noLowHpSound", false}, @@ -157,6 +158,7 @@ void registerSettings() { 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); From f856f871bb937c1aea63369e40e6df7b073a0f0a Mon Sep 17 00:00:00 2001 From: Pheenoh Date: Sun, 26 Apr 2026 13:55:32 -0600 Subject: [PATCH 09/15] Only show languages if PAL disc --- src/dusk/imgui/ImGuiPreLaunchWindow.cpp | 22 ++++++++++-------- src/dusk/imgui/ImGuiPreLaunchWindow.hpp | 1 + src/dusk/iso_validate.cpp | 31 +++++++++++++++++++++++++ src/dusk/iso_validate.hpp | 1 + 4 files changed, 45 insertions(+), 10 deletions(-) diff --git a/src/dusk/imgui/ImGuiPreLaunchWindow.cpp b/src/dusk/imgui/ImGuiPreLaunchWindow.cpp index 6347e9bc01..eef8c08e0d 100644 --- a/src/dusk/imgui/ImGuiPreLaunchWindow.cpp +++ b/src/dusk/imgui/ImGuiPreLaunchWindow.cpp @@ -75,6 +75,7 @@ void fileDialogCallback(void* userdata, const char* path, const char* error) { } self->m_selectedIsoPath = path; + self->m_isPal = iso::isPal(path); getSettings().backend.isoPath.setValue(self->m_selectedIsoPath); config::Save(); } @@ -92,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; } @@ -199,18 +201,18 @@ void ImGuiPreLaunchWindow::drawOptions() { false); } - // TODO: Only show if PAL disc selected? - // Language selection - 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(); + 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(); + ImGui::EndCombo(); + } } AuroraBackend configuredBackend = BACKEND_AUTO; 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/iso_validate.cpp b/src/dusk/iso_validate.cpp index 0b4a19ae9c..e83b3fd34e 100644 --- a/src/dusk/iso_validate.cpp +++ b/src/dusk/iso_validate.cpp @@ -17,6 +17,11 @@ 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 @@ -124,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 From 90c0bdded0816de41f35e718af70619f58131b8c Mon Sep 17 00:00:00 2001 From: Luke Street Date: Sun, 26 Apr 2026 14:02:28 -0600 Subject: [PATCH 10/15] Use GXDestroyCopyTex on map destroy Resolves #469 --- extern/aurora | 2 +- src/d/d_map.cpp | 6 ++++++ src/d/d_menu_dmap_map.cpp | 6 ++++++ src/d/d_menu_fmap_map.cpp | 6 ++++++ 4 files changed, 19 insertions(+), 1 deletion(-) diff --git a/extern/aurora b/extern/aurora index 8af9057689..a6a3d3a65a 160000 --- a/extern/aurora +++ b/extern/aurora @@ -1 +1 @@ -Subproject commit 8af9057689b9b85a55bbbd6218bc95304558fabd +Subproject commit a6a3d3a65ae0de6de8b60629cf47fd0f446c21cb diff --git a/src/d/d_map.cpp b/src/d/d_map.cpp index a5163091fd..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 @@ -1215,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_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_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; } From 0f9d563a3e68f42e67f6a4bf60c27db4df9482fd Mon Sep 17 00:00:00 2001 From: Pheenoh Date: Sun, 26 Apr 2026 15:30:51 -0600 Subject: [PATCH 11/15] cLib_offsetPos param aliasing fix --- src/SSystem/SComponent/c_lib.cpp | 11 +++++++++++ 1 file changed, 11 insertions(+) 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 } /** From a05d1a9ee61ba350ce26dbcc29f68b382ae171d4 Mon Sep 17 00:00:00 2001 From: Pheenoh Date: Sun, 26 Apr 2026 17:05:46 -0600 Subject: [PATCH 12/15] d_a_balloon score and interp fixes --- src/d/actor/d_a_balloon_2D.cpp | 17 ++++++++++++++--- src/d/actor/d_a_obj_balloon.cpp | 7 +++++++ 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/src/d/actor/d_a_balloon_2D.cpp b/src/d/actor/d_a_balloon_2D.cpp index 6b5b5a2bd2..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" @@ -438,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; @@ -446,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_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; } From 206c02b527993d5dfa4e2e9c1471821dedc47805 Mon Sep 17 00:00:00 2001 From: TakaRikka Date: Sun, 26 Apr 2026 16:51:10 -0700 Subject: [PATCH 13/15] fix igt timer --- src/d/actor/d_a_alink_demo.inc | 4 +-- src/d/d_bright_check.cpp | 5 +-- src/d/d_s_name.cpp | 5 +-- src/dusk/imgui/ImGuiConsole.hpp | 5 --- src/dusk/imgui/ImGuiMenuGame.cpp | 13 +++++--- src/dusk/imgui/ImGuiMenuGame.hpp | 52 +++++++++++++++++++++----------- src/f_op/f_op_overlap_req.cpp | 20 ++++++++++++ src/f_op/f_op_scene_req.cpp | 16 ---------- 8 files changed, 70 insertions(+), 50 deletions(-) diff --git a/src/d/actor/d_a_alink_demo.inc b/src/d/actor/d_a_alink_demo.inc index b46af5cacb..5ac30a3cb9 100644 --- a/src/d/actor/d_a_alink_demo.inc +++ b/src/d/actor/d_a_alink_demo.inc @@ -4009,8 +4009,8 @@ int daAlink_c::procGanonFinishInit() { #if TARGET_PC if (dusk::getSettings().game.speedrunMode) { - if (dusk::g_imguiConsole.isSpeedrunStart()) { - dusk::g_imguiConsole.stopSpeedrun(); + if (dusk::m_speedrunInfo.m_isRunStarted) { + dusk::m_speedrunInfo.stopRun(); } } #endif diff --git a/src/d/d_bright_check.cpp b/src/d/d_bright_check.cpp index 88d0b7a2d8..3ee98be5c7 100644 --- a/src/d/d_bright_check.cpp +++ b/src/d/d_bright_check.cpp @@ -145,8 +145,9 @@ void dBrightCheck_c::modeMove() { if (dusk::getSettings().game.speedrunMode && !dusk::getSettings().game.hideTvSettingsScreen) { // start a new run if a run isn't already in progress - if (!dusk::g_imguiConsole.isSpeedrunStart()) { - dusk::g_imguiConsole.startSpeedrun(); + if (!dusk::m_speedrunInfo.m_isRunStarted) { + dusk::ImGuiMenuGame::resetForSpeedrunMode(); + dusk::m_speedrunInfo.startRun(); } } #endif diff --git a/src/d/d_s_name.cpp b/src/d/d_s_name.cpp index 9a20ca6caa..6baa07da30 100644 --- a/src/d/d_s_name.cpp +++ b/src/d/d_s_name.cpp @@ -417,8 +417,9 @@ void dScnName_c::changeGameScene() { #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::g_imguiConsole.isSpeedrunStart()) { - dusk::g_imguiConsole.startSpeedrun(); + if (!dusk::m_speedrunInfo.m_isRunStarted) { + dusk::ImGuiMenuGame::resetForSpeedrunMode(); + dusk::m_speedrunInfo.startRun(); } } #endif diff --git a/src/dusk/imgui/ImGuiConsole.hpp b/src/dusk/imgui/ImGuiConsole.hpp index 5e9f2aa523..a6c8b48fc7 100644 --- a/src/dusk/imgui/ImGuiConsole.hpp +++ b/src/dusk/imgui/ImGuiConsole.hpp @@ -29,11 +29,6 @@ public: static bool CheckMenuViewToggle(ImGuiKey key, bool& active); void AddToast(std::string_view message, float duration = 3.f); - bool isSpeedrunStart() const { return m_menuGame.isRunStarted(); } - void startSpeedrun() { m_menuGame.startRun(); } - void stopSpeedrun() { m_menuGame.stopRun(); } - void incSpeedrunTotalLoadTime(OSTime time) { m_menuGame.incTotalLoadTime(time); } - private: struct Toast { std::string message; diff --git a/src/dusk/imgui/ImGuiMenuGame.cpp b/src/dusk/imgui/ImGuiMenuGame.cpp index 83b80869b4..7f114aa015 100644 --- a/src/dusk/imgui/ImGuiMenuGame.cpp +++ b/src/dusk/imgui/ImGuiMenuGame.cpp @@ -1051,6 +1051,8 @@ namespace dusk { getSettings().game.enableTurboKeybind.setValue(false); } + SpeedrunInfo m_speedrunInfo; + void ImGuiMenuGame::drawSpeedrunTimerOverlay() { if (!getSettings().game.speedrunMode) { return; @@ -1058,10 +1060,7 @@ namespace dusk { // 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.m_endTimestamp = 0; - m_speedrunInfo.m_startTimestamp = 0; - m_speedrunInfo.m_totalLoadTime = 0; - m_speedrunInfo.m_isRunStarted = false; + m_speedrunInfo.reset(); } // L+R+A+Z to manually stop timer @@ -1091,9 +1090,13 @@ namespace dusk { 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(elapsedTime - m_speedrunInfo.m_totalLoadTime)); + ImGuiStringViewText(GetFormattedTime(m_speedrunInfo.m_igtTimer)); } ImGui::End(); } diff --git a/src/dusk/imgui/ImGuiMenuGame.hpp b/src/dusk/imgui/ImGuiMenuGame.hpp index 795cf5f446..4fbfec8f76 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(); @@ -20,17 +53,6 @@ namespace dusk { static void ToggleFullscreen(); static void resetForSpeedrunMode(); - bool isRunStarted() const { return m_speedrunInfo.m_isRunStarted; } - void startRun() { - resetForSpeedrunMode(); - m_speedrunInfo.m_isRunStarted = true; - m_speedrunInfo.m_startTimestamp = OSGetTime(); - } - void stopRun() { - m_speedrunInfo.m_isRunStarted = false; - m_speedrunInfo.m_endTimestamp = OSGetTime() - m_speedrunInfo.m_startTimestamp; - } - void incTotalLoadTime(OSTime time) { m_speedrunInfo.m_totalLoadTime += time; } private: void drawAudioMenu(); @@ -55,13 +77,7 @@ namespace dusk { int m_inputOverlayCorner = 3; std::string m_controllerName; - struct { - bool m_showTimerWindow = false; - bool m_isRunStarted = false; - OSTime m_startTimestamp = 0; - OSTime m_endTimestamp = 0; - OSTime m_totalLoadTime = 0; - } m_speedrunInfo; + bool m_showTimerWindow = false; }; } 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 e4ce889266..abcb6ce848 100644 --- a/src/f_op/f_op_scene_req.cpp +++ b/src/f_op/f_op_scene_req.cpp @@ -40,10 +40,6 @@ static cPhs_Step fopScnRq_phase_IsDoneOverlap(scene_request_class* i_sceneReq) { static BOOL l_fopScnRq_IsUsingOfOverlap; -#if TARGET_PC -static OSTime l_fopScnRq_StartTime = 0; -#endif - static cPhs_Step fopScnRq_phase_Done(scene_request_class* i_sceneReq) { if (i_sceneReq->create_request.parameters != 1) { @@ -53,14 +49,6 @@ static cPhs_Step fopScnRq_phase_Done(scene_request_class* i_sceneReq) { } l_fopScnRq_IsUsingOfOverlap = FALSE; - #if TARGET_PC - if (dusk::getSettings().game.speedrunMode) { - if (dusk::g_imguiConsole.isSpeedrunStart()) { - dusk::g_imguiConsole.incSpeedrunTotalLoadTime(OSGetTime() - l_fopScnRq_StartTime); - } - } - #endif - return cPhs_NEXT_e; } @@ -101,10 +89,6 @@ static scene_request_class* fopScnRq_FadeRequest(s16 i_procname, u16 i_peektime) req = fopOvlpM_Request(i_procname, i_peektime); if (req != NULL) { l_fopScnRq_IsUsingOfOverlap = TRUE; - - #if TARGET_PC - l_fopScnRq_StartTime = OSGetTime(); - #endif } } From 23a91a37be19b596e9bbd03e513309b84a62c67a Mon Sep 17 00:00:00 2001 From: Luke Street Date: Sun, 26 Apr 2026 21:01:32 -0600 Subject: [PATCH 14/15] Kinda crappy initial controller support --- extern/aurora | 2 +- src/dusk/imgui/ImGuiConsole.cpp | 33 +++++++++- src/dusk/imgui/ImGuiConsole.hpp | 1 + src/dusk/imgui/ImGuiMenuGame.cpp | 100 ++++++++++++++++++++++++++----- src/dusk/imgui/ImGuiMenuGame.hpp | 3 + 5 files changed, 120 insertions(+), 19 deletions(-) diff --git a/extern/aurora b/extern/aurora index a6a3d3a65a..7784b6fc95 160000 --- a/extern/aurora +++ b/extern/aurora @@ -1 +1 @@ -Subproject commit a6a3d3a65ae0de6de8b60629cf47fd0f446c21cb +Subproject commit 7784b6fc95568551499c87bd093b78d86e194eba diff --git a/src/dusk/imgui/ImGuiConsole.cpp b/src/dusk/imgui/ImGuiConsole.cpp index 6bca83482e..a94c7d1f5b 100644 --- a/src/dusk/imgui/ImGuiConsole.cpp +++ b/src/dusk/imgui/ImGuiConsole.cpp @@ -56,6 +56,21 @@ ImGuiWindow* FindDragScrollWindow(ImGuiWindow* window) { } return nullptr; } + +void FocusLastMenuBarItem() { + ImGuiContext& g = *ImGui::GetCurrentContext(); + ImGuiWindow* window = ImGui::GetCurrentWindow(); + const ImGuiID itemId = g.LastItemData.ID; + if (window == nullptr || itemId == 0) { + return; + } + + ImGui::FocusWindow(window); + ImGui::SetNavID(itemId, ImGuiNavLayer_Menu, g.CurrentFocusScopeId, + ImGui::WindowRectAbsToRel(window, g.LastItemData.NavRect)); + ImGui::SetNavCursorVisibleAfterMove(); + g.NavHighlightItemUnderNav = true; +} } // namespace namespace dusk { @@ -329,7 +344,17 @@ namespace dusk { } m_isHidden = !getSettings().backend.duskMenuOpen; - bool showMenu = !dusk::IsGameLaunched || !CheckMenuViewToggle(ImGuiKey_F1, m_isHidden); + if (dusk::IsGameLaunched) { + if (ImGui::IsKeyPressed(ImGuiKey_F1)) { + m_isHidden = !m_isHidden; + } + if (ImGui::IsKeyPressed(ImGuiKey_GamepadBack)) { + m_isHidden = !m_isHidden; + m_focusMenuBar = !m_isHidden; + } + } + + bool showMenu = !dusk::IsGameLaunched || !m_isHidden; if (dusk::IsGameLaunched) { const bool menuOpen = !m_isHidden; if (getSettings().backend.duskMenuOpen != menuOpen) { @@ -343,6 +368,10 @@ namespace dusk { ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(0.0f, 0.0f, 0.0f, 0.0f)); if (showMenu && ImGui::BeginMainMenuBar()) { m_menuGame.draw(); + if (m_focusMenuBar) { + FocusLastMenuBarItem(); + m_focusMenuBar = false; + } m_menuTools.draw(); const auto fpsLabel = @@ -367,7 +396,7 @@ namespace dusk { if (dusk::IsGameLaunched && !m_isLaunchInitialized) { m_toasts.emplace_back(ImGui::GetIO().MouseSource == ImGuiMouseSource_TouchScreen ? "Tap to toggle menu"s : - "Press F1 to toggle menu"s, + "Press F1 or Minus/Back to toggle menu"s, 2.5f); m_isLaunchInitialized = true; if (getSettings().game.liveSplitEnabled) { diff --git a/src/dusk/imgui/ImGuiConsole.hpp b/src/dusk/imgui/ImGuiConsole.hpp index a6c8b48fc7..fc7b4c51bc 100644 --- a/src/dusk/imgui/ImGuiConsole.hpp +++ b/src/dusk/imgui/ImGuiConsole.hpp @@ -41,6 +41,7 @@ private: float mouseHideTimer = 0.0f; bool m_isHidden = true; + bool m_focusMenuBar = false; bool m_isLaunchInitialized = false; bool m_touchTapActive = false; bool m_touchTapMoved = false; diff --git a/src/dusk/imgui/ImGuiMenuGame.cpp b/src/dusk/imgui/ImGuiMenuGame.cpp index d0da643b0c..251fabd227 100644 --- a/src/dusk/imgui/ImGuiMenuGame.cpp +++ b/src/dusk/imgui/ImGuiMenuGame.cpp @@ -23,6 +23,15 @@ namespace { constexpr int kInternalResolutionScaleMax = 12; + +bool is_controller_neutral(int port) { + if (port < 0) { + return true; + } + + return PADGetNativeButtonPressed(port) == -1 && + PADGetNativeAxisPulled(port).nativeAxis == -1; +} } // namespace namespace aurora::gx { @@ -196,7 +205,7 @@ namespace dusk { 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"); @@ -642,39 +651,90 @@ namespace dusk { void ImGuiMenuGame::windowControllerConfig() { if (!m_showControllerConfig) { + if (m_controllerConfig.m_isReading || + m_controllerConfig.m_suppressRemapActivationUntilRelease) + { + m_controllerConfig.m_isReading = false; + m_controllerConfig.m_pendingButtonMapping = nullptr; + m_controllerConfig.m_pendingAxisMapping = nullptr; + m_controllerConfig.m_pendingPort = -1; + m_controllerConfig.m_waitForInputRelease = false; + m_controllerConfig.m_suppressRemapActivationUntilRelease = false; + m_controllerConfig.m_suppressRemapActivationPort = -1; + PADBlockInput(false); + } return; } + bool suppressRemapActivationThisFrame = m_controllerConfig.m_suppressRemapActivationUntilRelease; + if (m_controllerConfig.m_suppressRemapActivationUntilRelease && + is_controller_neutral(m_controllerConfig.m_suppressRemapActivationPort)) + { + m_controllerConfig.m_suppressRemapActivationUntilRelease = false; + m_controllerConfig.m_suppressRemapActivationPort = -1; + PADBlockInput(false); + } + + if ((m_controllerConfig.m_pendingButtonMapping != nullptr || + m_controllerConfig.m_pendingAxisMapping != nullptr) && + m_controllerConfig.m_waitForInputRelease) + { + m_controllerConfig.m_waitForInputRelease = + !is_controller_neutral(m_controllerConfig.m_pendingPort); + } + // if pending for a button mapping, check to set new input - if (m_controllerConfig.m_pendingButtonMapping != nullptr) { + if (m_controllerConfig.m_pendingButtonMapping != nullptr && + !m_controllerConfig.m_waitForInputRelease) + { s32 nativeButton = PADGetNativeButtonPressed(m_controllerConfig.m_pendingPort); if (nativeButton != -1) { + const int suppressPort = m_controllerConfig.m_pendingPort; m_controllerConfig.m_pendingButtonMapping->nativeButton = nativeButton; m_controllerConfig.m_pendingButtonMapping = nullptr; m_controllerConfig.m_pendingPort = -1; - PADBlockInput(false); + m_controllerConfig.m_isReading = false; + m_controllerConfig.m_waitForInputRelease = false; + m_controllerConfig.m_suppressRemapActivationUntilRelease = true; + m_controllerConfig.m_suppressRemapActivationPort = suppressPort; + suppressRemapActivationThisFrame = true; + PADBlockInput(true); PADSerializeMappings(); } } // if pending for an axis mapping, check to set new input - if (m_controllerConfig.m_pendingAxisMapping != nullptr) { + if (m_controllerConfig.m_pendingAxisMapping != nullptr && + !m_controllerConfig.m_waitForInputRelease) + { auto nativeAxis = PADGetNativeAxisPulled(m_controllerConfig.m_pendingPort); if (nativeAxis.nativeAxis != -1) { + const int suppressPort = m_controllerConfig.m_pendingPort; m_controllerConfig.m_pendingAxisMapping->nativeAxis = nativeAxis; m_controllerConfig.m_pendingAxisMapping->nativeButton = -1; m_controllerConfig.m_pendingAxisMapping = nullptr; m_controllerConfig.m_pendingPort = -1; - PADBlockInput(false); + m_controllerConfig.m_isReading = false; + m_controllerConfig.m_waitForInputRelease = false; + m_controllerConfig.m_suppressRemapActivationUntilRelease = true; + m_controllerConfig.m_suppressRemapActivationPort = suppressPort; + suppressRemapActivationThisFrame = true; + PADBlockInput(true); PADSerializeMappings(); } else { auto nativeButton = PADGetNativeButtonPressed(m_controllerConfig.m_pendingPort); if (nativeButton != -1) { + const int suppressPort = m_controllerConfig.m_pendingPort; m_controllerConfig.m_pendingAxisMapping->nativeAxis = {-1, AXIS_SIGN_POSITIVE}; m_controllerConfig.m_pendingAxisMapping->nativeButton = nativeButton; m_controllerConfig.m_pendingAxisMapping = nullptr; m_controllerConfig.m_pendingPort = -1; - PADBlockInput(false); + m_controllerConfig.m_isReading = false; + m_controllerConfig.m_waitForInputRelease = false; + m_controllerConfig.m_suppressRemapActivationUntilRelease = true; + m_controllerConfig.m_suppressRemapActivationPort = suppressPort; + suppressRemapActivationThisFrame = true; + PADBlockInput(true); PADSerializeMappings(); } } @@ -710,6 +770,10 @@ namespace dusk { m_controllerConfig.m_pendingButtonMapping = nullptr; m_controllerConfig.m_pendingAxisMapping = nullptr; m_controllerConfig.m_pendingPort = -1; + m_controllerConfig.m_waitForInputRelease = false; + m_controllerConfig.m_isReading = false; + m_controllerConfig.m_suppressRemapActivationUntilRelease = false; + m_controllerConfig.m_suppressRemapActivationPort = -1; PADBlockInput(false); } @@ -786,7 +850,7 @@ namespace dusk { std::string dispName; if (m_controllerConfig.m_isReading && m_controllerConfig.m_pendingButtonMapping == &btnMappingList[i]) { - dispName = fmt::format("Press a Key...##{}", btnName); + dispName = fmt::format("{}##{}", m_controllerConfig.m_waitForInputRelease ? "Release..." : "Press a Key...", btnName); } else { const char* nativeName = GetNameForGamepadButton(gamepad, btnMappingList[i].nativeButton); if (nativeName == nullptr) { @@ -797,10 +861,11 @@ namespace dusk { bool pressed = ImGui::Button(dispName.c_str(), btnSize); - if (pressed) { + if (pressed && !m_controllerConfig.m_isReading && !suppressRemapActivationThisFrame) { m_controllerConfig.m_isReading = true; m_controllerConfig.m_pendingPort = m_controllerConfig.m_selectedPort; m_controllerConfig.m_pendingButtonMapping = &btnMappingList[i]; + m_controllerConfig.m_waitForInputRelease = true; PADBlockInput(true); } } @@ -830,17 +895,18 @@ namespace dusk { std::string dispName; if (m_controllerConfig.m_isReading && m_controllerConfig.m_pendingAxisMapping == &axisMappingList[trigger]) { - dispName = fmt::format("Press a Key...##{}", axisName); + dispName = fmt::format("{}##{}", m_controllerConfig.m_waitForInputRelease ? "Release..." : "Press a Key...", axisName); } else { dispName = fmt::format("{0}##-{1}", PADGetNativeAxisName(axisMappingList[trigger].nativeAxis), trigger); } bool pressed = ImGui::Button(dispName.c_str(), btnSize); - if (pressed) { + if (pressed && !m_controllerConfig.m_isReading && !suppressRemapActivationThisFrame) { m_controllerConfig.m_isReading = true; m_controllerConfig.m_pendingPort = m_controllerConfig.m_selectedPort; m_controllerConfig.m_pendingAxisMapping = &axisMappingList[trigger]; + m_controllerConfig.m_waitForInputRelease = true; PADBlockInput(true); } } @@ -897,7 +963,7 @@ namespace dusk { std::string dispName; if (m_controllerConfig.m_isReading && m_controllerConfig.m_pendingAxisMapping == &axisMappingList[axis]) { - dispName = fmt::format("Press a Key...##{}", label); + dispName = fmt::format("{}##{}", m_controllerConfig.m_waitForInputRelease ? "Release..." : "Press a Key...", label); } else { if (axisMappingList[axis].nativeAxis.nativeAxis != -1) { const char* signStr; @@ -916,10 +982,11 @@ namespace dusk { } bool pressed = ImGui::Button(dispName.c_str(), btnSize); - if (pressed) { + if (pressed && !m_controllerConfig.m_isReading && !suppressRemapActivationThisFrame) { m_controllerConfig.m_isReading = true; m_controllerConfig.m_pendingPort = m_controllerConfig.m_selectedPort; m_controllerConfig.m_pendingAxisMapping = &axisMappingList[axis]; + m_controllerConfig.m_waitForInputRelease = true; PADBlockInput(true); } } @@ -960,7 +1027,7 @@ namespace dusk { std::string dispName; if (m_controllerConfig.m_isReading && m_controllerConfig.m_pendingAxisMapping == &axisMappingList[axis]) { - dispName = fmt::format("Press a Key...##sub{}", label); + dispName = fmt::format("{}##sub{}", m_controllerConfig.m_waitForInputRelease ? "Release..." : "Press a Key...", label); } else { if (axisMappingList[axis].nativeAxis.nativeAxis != -1) { const char* signStr; @@ -979,10 +1046,11 @@ namespace dusk { } bool pressed = ImGui::Button(fmt::format("{0}##sub{1}", dispName, label).c_str(), btnSize); - if (pressed) { + if (pressed && !m_controllerConfig.m_isReading && !suppressRemapActivationThisFrame) { m_controllerConfig.m_isReading = true; m_controllerConfig.m_pendingPort = m_controllerConfig.m_selectedPort; m_controllerConfig.m_pendingAxisMapping = &axisMappingList[axis]; + m_controllerConfig.m_waitForInputRelease = true; PADBlockInput(true); } } @@ -1013,7 +1081,7 @@ namespace dusk { PADSerializeMappings(); } } - + if (PADSupportsRumbleIntensity(m_controllerConfig.m_selectedPort)) { ImGuiBeginGroupPanel("Rumble Intensity", ImVec2(150 * scale, -1)); u16 low; @@ -1032,7 +1100,7 @@ namespace dusk { 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(); diff --git a/src/dusk/imgui/ImGuiMenuGame.hpp b/src/dusk/imgui/ImGuiMenuGame.hpp index e54541a010..c902c92359 100644 --- a/src/dusk/imgui/ImGuiMenuGame.hpp +++ b/src/dusk/imgui/ImGuiMenuGame.hpp @@ -68,6 +68,9 @@ namespace dusk { PADButtonMapping* m_pendingButtonMapping = nullptr; PADAxisMapping* m_pendingAxisMapping = nullptr; int m_pendingPort = -1; + bool m_waitForInputRelease = false; + bool m_suppressRemapActivationUntilRelease = false; + int m_suppressRemapActivationPort = -1; bool m_isRumbling = false; } m_controllerConfig; From 5f33489465bc3d2da0de6daa7b7eabe6878d674f Mon Sep 17 00:00:00 2001 From: Irastris Date: Mon, 27 Apr 2026 01:04:45 -0400 Subject: [PATCH 15/15] Register interp callback for d_a_obj_lv8Lift --- include/d/actor/d_a_obj_lv8Lift.h | 3 +++ src/d/actor/d_a_obj_lv8Lift.cpp | 41 +++++++++++++++++++++++++++++++ 2 files changed, 44 insertions(+) diff --git a/include/d/actor/d_a_obj_lv8Lift.h b/include/d/actor/d_a_obj_lv8Lift.h index c5a8ae9f27..c327f4cf3c 100644 --- a/include/d/actor/d_a_obj_lv8Lift.h +++ b/include/d/actor/d_a_obj_lv8Lift.h @@ -58,6 +58,9 @@ public: void setNextPoint(); int Draw(); int Delete(); +#if TARGET_PC + friend void daL8Lift_interp_callback(bool isSimFrame, void* pUserWork); +#endif u8 getPthID() { return fopAcM_GetParamBit(this, 0, 8); } u8 getMoveSpeed() { return fopAcM_GetParamBit(this, 8, 4); } diff --git a/src/d/actor/d_a_obj_lv8Lift.cpp b/src/d/actor/d_a_obj_lv8Lift.cpp index 4f475a626c..8fa9422325 100644 --- a/src/d/actor/d_a_obj_lv8Lift.cpp +++ b/src/d/actor/d_a_obj_lv8Lift.cpp @@ -10,6 +10,10 @@ #include "d/d_path.h" #include "d/d_bg_w.h" +#if TARGET_PC +#include "dusk/frame_interpolation.h" +#endif + daL8Lift_HIO_c::daL8Lift_HIO_c() { mStopDisappearTime = 30; mStartMoveTime = 60; @@ -380,7 +384,44 @@ void daL8Lift_c::setNextPoint() { mCurrentPoint = next_point; } +#if TARGET_PC +void daL8Lift_interp_callback(bool isSimFrame, void* pUserWork) { + daL8Lift_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 daL8Lift_c::Draw() { +#if TARGET_PC + dusk::frame_interp::add_interpolation_callback(&daL8Lift_interp_callback, this); +#endif + g_env_light.settingTevStruct(16, ¤t.pos, &tevStr); g_env_light.setLightTevColorType_MAJI(mpModel, &tevStr); J3DModelData* modelData = mpModel->getModelData();