From e7ecc0d194b4d6a572925e0c30d64a4febd68003 Mon Sep 17 00:00:00 2001 From: madeline Date: Mon, 6 Apr 2026 00:32:39 -0700 Subject: [PATCH 01/19] first run presets --- files.cmake | 2 + include/dusk/config.hpp | 7 ++ src/dusk/config.cpp | 6 ++ src/dusk/imgui/ImGuiConsole.cpp | 4 + src/dusk/imgui/ImGuiConsole.hpp | 2 + src/dusk/imgui/ImGuiEngine.cpp | 2 +- src/dusk/imgui/ImGuiFirstRunPreset.cpp | 130 +++++++++++++++++++++++++ src/dusk/imgui/ImGuiFirstRunPreset.hpp | 14 +++ 8 files changed, 166 insertions(+), 1 deletion(-) create mode 100644 src/dusk/imgui/ImGuiFirstRunPreset.cpp create mode 100644 src/dusk/imgui/ImGuiFirstRunPreset.hpp diff --git a/files.cmake b/files.cmake index 7d07408613..c6cf3b5d3d 100644 --- a/files.cmake +++ b/files.cmake @@ -1358,6 +1358,8 @@ set(DUSK_FILES src/dusk/imgui/ImGuiMenuTools.hpp src/dusk/imgui/ImGuiMenuEnhancements.cpp src/dusk/imgui/ImGuiMenuEnhancements.hpp + src/dusk/imgui/ImGuiFirstRunPreset.hpp + src/dusk/imgui/ImGuiFirstRunPreset.cpp src/dusk/imgui/ImGuiProcessOverlay.cpp src/dusk/imgui/ImGuiCameraOverlay.cpp src/dusk/imgui/ImGuiHeapOverlay.cpp diff --git a/include/dusk/config.hpp b/include/dusk/config.hpp index 258e012f2b..0fd0ad8556 100644 --- a/include/dusk/config.hpp +++ b/include/dusk/config.hpp @@ -111,6 +111,13 @@ void Save(); */ ConfigVarBase* GetConfigVar(std::string_view name); +/** + * \brief Returns true if no config file was found on the last LoadFromUserPreferences() call. + * + * For detect first run to prompt the user to choose a preset. + */ +bool WasConfigFileMissing(); + template const ConfigImplBase* GetConfigImpl() { static ConfigImpl config; diff --git a/src/dusk/config.cpp b/src/dusk/config.cpp index 159706fac8..1fa2e689b6 100644 --- a/src/dusk/config.cpp +++ b/src/dusk/config.cpp @@ -21,6 +21,7 @@ aurora::Module DuskConfigLog("dusk::config"); static absl::flat_hash_map RegisteredConfigVars; static bool RegistrationDone; +static bool s_configFileMissing = false; static std::string GetConfigJsonPath() { return fmt::format("{}{}", configPath, ConfigFileName); @@ -187,6 +188,7 @@ void dusk::config::LoadFromFileName(const char* path) { } catch (const std::system_error& e) { if (e.code() == std::errc::no_such_file_or_directory) { DuskConfigLog.info("Config file did not exist, staying with defaults"); + s_configFileMissing = true; } else { DuskConfigLog.error("Failed to load from config! {}", e.what()); } @@ -212,6 +214,10 @@ void dusk::config::Save() { io::FileStream::WriteAllText(configJsonPath.c_str(), j.dump(4)); } +bool dusk::config::WasConfigFileMissing() { + return s_configFileMissing; +} + ConfigVarBase* dusk::config::GetConfigVar(std::string_view name) { const auto configVar = RegisteredConfigVars.find(name); if (configVar != RegisteredConfigVars.end()) { diff --git a/src/dusk/imgui/ImGuiConsole.cpp b/src/dusk/imgui/ImGuiConsole.cpp index fe5d3e7c9a..7140af5fc7 100644 --- a/src/dusk/imgui/ImGuiConsole.cpp +++ b/src/dusk/imgui/ImGuiConsole.cpp @@ -184,6 +184,10 @@ namespace dusk { m_isLaunchInitialized = true; } + if (config::WasConfigFileMissing()) { + m_firstRunPreset.draw(); + } + getTransientSettings().skipFrameRateLimit = getSettings().game.enableTurboKeybind && ImGui::IsKeyDown(ImGuiKey_Tab); if ((ImGui::IsKeyDown(ImGuiKey_LeftCtrl) || ImGui::IsKeyDown(ImGuiKey_RightCtrl)) && diff --git a/src/dusk/imgui/ImGuiConsole.hpp b/src/dusk/imgui/ImGuiConsole.hpp index 27032ed39b..75da71f6a3 100644 --- a/src/dusk/imgui/ImGuiConsole.hpp +++ b/src/dusk/imgui/ImGuiConsole.hpp @@ -5,6 +5,7 @@ #include #include +#include "ImGuiFirstRunPreset.hpp" #include "ImGuiMenuEnhancements.hpp" #include "ImGuiMenuGame.hpp" #include "ImGuiMenuTools.hpp" @@ -32,6 +33,7 @@ private: bool m_isLaunchInitialized = false; std::deque m_toasts; + ImGuiFirstRunPreset m_firstRunPreset; ImGuiMenuGame m_menuGame; ImGuiMenuEnhancements m_menuEnhancements; diff --git a/src/dusk/imgui/ImGuiEngine.cpp b/src/dusk/imgui/ImGuiEngine.cpp index a6abc5b0b9..37e0415f41 100644 --- a/src/dusk/imgui/ImGuiEngine.cpp +++ b/src/dusk/imgui/ImGuiEngine.cpp @@ -145,7 +145,7 @@ void ImGuiEngine_Initialize(float scale) { colors[ImGuiCol_NavHighlight] = ImVec4(0.26f, 0.59f, 0.98f, 1.00f); colors[ImGuiCol_NavWindowingHighlight] = ImVec4(1.00f, 1.00f, 1.00f, 0.70f); colors[ImGuiCol_NavWindowingDimBg] = ImVec4(0.80f, 0.80f, 0.80f, 0.20f); - colors[ImGuiCol_ModalWindowDimBg] = ImVec4(0.80f, 0.80f, 0.80f, 0.35f); + colors[ImGuiCol_ModalWindowDimBg] = ImVec4(0.00f, 0.00f, 0.00f, 0.60f); } diff --git a/src/dusk/imgui/ImGuiFirstRunPreset.cpp b/src/dusk/imgui/ImGuiFirstRunPreset.cpp new file mode 100644 index 0000000000..275af7a0fc --- /dev/null +++ b/src/dusk/imgui/ImGuiFirstRunPreset.cpp @@ -0,0 +1,130 @@ +#include "ImGuiFirstRunPreset.hpp" + +#include "imgui.h" +#include "ImGuiConsole.hpp" +#include "ImGuiEngine.hpp" +#include "dusk/settings.h" +#include "dusk/config.hpp" + +namespace dusk { + +static void ApplyPresetVanilla() { + auto& s = getSettings(); + // todo: lock aspect ratio +} + +static void ApplyPresetDefault() { + auto& s = getSettings(); + // todo: unlock aspect ratio + // todo: instant saving + s.game.hideTvSettingsScreen.setValue(true); +} + +static void ApplyPresetQoL() { + auto& s = getSettings(); + // todo: unlock aspect ratio + // todo: instant saving + // todo: more save files + // todo: autosave + s.game.enableQuickTransform.setValue(true); + s.game.hideTvSettingsScreen.setValue(true); + s.game.noReturnRupees.setValue(true); + s.game.disableRupeeCutscenes.setValue(true); + s.game.noSwordRecoil.setValue(true); + s.game.fastClimbing.setValue(true); + s.game.noMissClimbing.setValue(true); + s.game.fastTears.setValue(true); + s.game.noLowHpSound.setValue(true); + s.game.midnasLamentNonStop.setValue(true); + s.game.enableFastIronBoots.setValue(true); + s.game.canTransformAnywhere.setValue(true); + s.game.enableTurboKeybind.setValue(true); +} + +// ========================================================================= + +void ImGuiFirstRunPreset::draw() { + const char* modalTitle = "Welcome to Dusk!"; + + if (m_done) return; + + if (!m_opened) { + ImGui::OpenPopup(modalTitle); + m_opened = true; + } + + const ImGuiViewport* viewport = ImGui::GetMainViewport(); + ImGui::SetNextWindowPos(viewport->GetCenter(), ImGuiCond_Always, ImVec2(0.5f, 0.5f)); + ImGui::SetNextWindowSize(ImVec2(800.0f * ImGuiScale(), 0.0f), ImGuiCond_Always); + + if (!ImGui::BeginPopupModal(modalTitle, nullptr, ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoMove)) { + // force the user to actually pick one, and not just hit escape to skip the dialog + m_opened = false; + return; + } + + ImGui::TextWrapped("Choose a preset to get started. You can change any setting later from the Enhancements menu."); + ImGui::Spacing(); + ImGui::Separator(); + ImGui::Spacing(); + + int chosen = -1; + + if (ImGui::BeginTable("##presets", 5, ImGuiTableFlags_None)) { + ImGui::TableSetupColumn(nullptr, ImGuiTableColumnFlags_WidthStretch); + ImGui::TableSetupColumn(nullptr, ImGuiTableColumnFlags_WidthFixed, 16.0f * ImGuiScale()); + ImGui::TableSetupColumn(nullptr, ImGuiTableColumnFlags_WidthStretch); + ImGui::TableSetupColumn(nullptr, ImGuiTableColumnFlags_WidthFixed, 16.0f * ImGuiScale()); + ImGui::TableSetupColumn(nullptr, ImGuiTableColumnFlags_WidthStretch); + + ImGui::TableNextRow(); + + ImGui::PushFont(ImGuiEngine::fontLarge); + + ImGui::TableSetColumnIndex(0); + if (ImGui::Button("Vanilla##btn", ImVec2(ImGui::GetContentRegionAvail().x, 80.0f * ImGuiScale()))) { + chosen = 0; + } + + ImGui::TableSetColumnIndex(2); + if (ImGui::Button("Default##btn", ImVec2(ImGui::GetContentRegionAvail().x, 80.0f * ImGuiScale()))) { + chosen = 1; + } + + ImGui::TableSetColumnIndex(4); + if (ImGui::Button("Quality of Life##btn", ImVec2(ImGui::GetContentRegionAvail().x, 80.0f * ImGuiScale()))) { + chosen = 2; + } + + ImGui::PopFont(); + + ImGui::TableNextRow(); + + ImGui::TableSetColumnIndex(0); + ImGui::Spacing(); + ImGui::TextWrapped("All enhancements disabled. Plays closest to the original game; good for speedrunning or simple nostalgia!"); + + ImGui::TableSetColumnIndex(2); + ImGui::Spacing(); + ImGui::TextWrapped("Some enhancements enabled, maintaining a vanilla feel. A good starting point for most players!"); + + ImGui::TableSetColumnIndex(4); + ImGui::Spacing(); + ImGui::TextWrapped("Many quality of life enhancements enabled. Good for seasoned players!"); + + ImGui::EndTable(); + } + + if (chosen >= 0) { + if (chosen == 0) ApplyPresetVanilla(); + if (chosen == 1) ApplyPresetDefault(); + if (chosen == 2) ApplyPresetQoL(); + config::Save(); + m_done = true; + ImGui::CloseCurrentPopup(); + } + + ImGui::EndPopup(); +} + +} // namespace dusk diff --git a/src/dusk/imgui/ImGuiFirstRunPreset.hpp b/src/dusk/imgui/ImGuiFirstRunPreset.hpp new file mode 100644 index 0000000000..33ba6b3d0f --- /dev/null +++ b/src/dusk/imgui/ImGuiFirstRunPreset.hpp @@ -0,0 +1,14 @@ +#pragma once + +namespace dusk { + +class ImGuiFirstRunPreset { +public: + void draw(); + +private: + bool m_opened = false; + bool m_done = false; +}; + +} // namespace dusk From e49c1989af08db0a3e96416a965b775c4159b720 Mon Sep 17 00:00:00 2001 From: madeline Date: Mon, 6 Apr 2026 00:37:17 -0700 Subject: [PATCH 02/19] turn off turbo keybind by default --- src/dusk/imgui/ImGuiFirstRunPreset.cpp | 1 - src/dusk/settings.cpp | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/dusk/imgui/ImGuiFirstRunPreset.cpp b/src/dusk/imgui/ImGuiFirstRunPreset.cpp index 275af7a0fc..6ac6791455 100644 --- a/src/dusk/imgui/ImGuiFirstRunPreset.cpp +++ b/src/dusk/imgui/ImGuiFirstRunPreset.cpp @@ -38,7 +38,6 @@ static void ApplyPresetQoL() { s.game.midnasLamentNonStop.setValue(true); s.game.enableFastIronBoots.setValue(true); s.game.canTransformAnywhere.setValue(true); - s.game.enableTurboKeybind.setValue(true); } // ========================================================================= diff --git a/src/dusk/settings.cpp b/src/dusk/settings.cpp index 278f783dbf..6a9887c27f 100644 --- a/src/dusk/settings.cpp +++ b/src/dusk/settings.cpp @@ -51,7 +51,7 @@ UserSettings g_userSettings = { .restoreWiiGlitches {"game.restoreWiiGlitches", false}, // Controls - .enableTurboKeybind {"game.enableTurboKeybind", true}, + .enableTurboKeybind {"game.enableTurboKeybind", false}, }, }; From 841b1bc563556076fa192125bc11af74fccf99da Mon Sep 17 00:00:00 2001 From: madeline Date: Mon, 6 Apr 2026 00:32:39 -0700 Subject: [PATCH 03/19] first run presets --- files.cmake | 2 + include/dusk/config.hpp | 7 ++ src/dusk/config.cpp | 6 ++ src/dusk/imgui/ImGuiConsole.cpp | 4 + src/dusk/imgui/ImGuiConsole.hpp | 2 + src/dusk/imgui/ImGuiEngine.cpp | 2 +- src/dusk/imgui/ImGuiFirstRunPreset.cpp | 130 +++++++++++++++++++++++++ src/dusk/imgui/ImGuiFirstRunPreset.hpp | 14 +++ 8 files changed, 166 insertions(+), 1 deletion(-) create mode 100644 src/dusk/imgui/ImGuiFirstRunPreset.cpp create mode 100644 src/dusk/imgui/ImGuiFirstRunPreset.hpp diff --git a/files.cmake b/files.cmake index 7d07408613..c6cf3b5d3d 100644 --- a/files.cmake +++ b/files.cmake @@ -1358,6 +1358,8 @@ set(DUSK_FILES src/dusk/imgui/ImGuiMenuTools.hpp src/dusk/imgui/ImGuiMenuEnhancements.cpp src/dusk/imgui/ImGuiMenuEnhancements.hpp + src/dusk/imgui/ImGuiFirstRunPreset.hpp + src/dusk/imgui/ImGuiFirstRunPreset.cpp src/dusk/imgui/ImGuiProcessOverlay.cpp src/dusk/imgui/ImGuiCameraOverlay.cpp src/dusk/imgui/ImGuiHeapOverlay.cpp diff --git a/include/dusk/config.hpp b/include/dusk/config.hpp index 258e012f2b..0fd0ad8556 100644 --- a/include/dusk/config.hpp +++ b/include/dusk/config.hpp @@ -111,6 +111,13 @@ void Save(); */ ConfigVarBase* GetConfigVar(std::string_view name); +/** + * \brief Returns true if no config file was found on the last LoadFromUserPreferences() call. + * + * For detect first run to prompt the user to choose a preset. + */ +bool WasConfigFileMissing(); + template const ConfigImplBase* GetConfigImpl() { static ConfigImpl config; diff --git a/src/dusk/config.cpp b/src/dusk/config.cpp index 159706fac8..1fa2e689b6 100644 --- a/src/dusk/config.cpp +++ b/src/dusk/config.cpp @@ -21,6 +21,7 @@ aurora::Module DuskConfigLog("dusk::config"); static absl::flat_hash_map RegisteredConfigVars; static bool RegistrationDone; +static bool s_configFileMissing = false; static std::string GetConfigJsonPath() { return fmt::format("{}{}", configPath, ConfigFileName); @@ -187,6 +188,7 @@ void dusk::config::LoadFromFileName(const char* path) { } catch (const std::system_error& e) { if (e.code() == std::errc::no_such_file_or_directory) { DuskConfigLog.info("Config file did not exist, staying with defaults"); + s_configFileMissing = true; } else { DuskConfigLog.error("Failed to load from config! {}", e.what()); } @@ -212,6 +214,10 @@ void dusk::config::Save() { io::FileStream::WriteAllText(configJsonPath.c_str(), j.dump(4)); } +bool dusk::config::WasConfigFileMissing() { + return s_configFileMissing; +} + ConfigVarBase* dusk::config::GetConfigVar(std::string_view name) { const auto configVar = RegisteredConfigVars.find(name); if (configVar != RegisteredConfigVars.end()) { diff --git a/src/dusk/imgui/ImGuiConsole.cpp b/src/dusk/imgui/ImGuiConsole.cpp index fe5d3e7c9a..7140af5fc7 100644 --- a/src/dusk/imgui/ImGuiConsole.cpp +++ b/src/dusk/imgui/ImGuiConsole.cpp @@ -184,6 +184,10 @@ namespace dusk { m_isLaunchInitialized = true; } + if (config::WasConfigFileMissing()) { + m_firstRunPreset.draw(); + } + getTransientSettings().skipFrameRateLimit = getSettings().game.enableTurboKeybind && ImGui::IsKeyDown(ImGuiKey_Tab); if ((ImGui::IsKeyDown(ImGuiKey_LeftCtrl) || ImGui::IsKeyDown(ImGuiKey_RightCtrl)) && diff --git a/src/dusk/imgui/ImGuiConsole.hpp b/src/dusk/imgui/ImGuiConsole.hpp index 27032ed39b..75da71f6a3 100644 --- a/src/dusk/imgui/ImGuiConsole.hpp +++ b/src/dusk/imgui/ImGuiConsole.hpp @@ -5,6 +5,7 @@ #include #include +#include "ImGuiFirstRunPreset.hpp" #include "ImGuiMenuEnhancements.hpp" #include "ImGuiMenuGame.hpp" #include "ImGuiMenuTools.hpp" @@ -32,6 +33,7 @@ private: bool m_isLaunchInitialized = false; std::deque m_toasts; + ImGuiFirstRunPreset m_firstRunPreset; ImGuiMenuGame m_menuGame; ImGuiMenuEnhancements m_menuEnhancements; diff --git a/src/dusk/imgui/ImGuiEngine.cpp b/src/dusk/imgui/ImGuiEngine.cpp index a6abc5b0b9..37e0415f41 100644 --- a/src/dusk/imgui/ImGuiEngine.cpp +++ b/src/dusk/imgui/ImGuiEngine.cpp @@ -145,7 +145,7 @@ void ImGuiEngine_Initialize(float scale) { colors[ImGuiCol_NavHighlight] = ImVec4(0.26f, 0.59f, 0.98f, 1.00f); colors[ImGuiCol_NavWindowingHighlight] = ImVec4(1.00f, 1.00f, 1.00f, 0.70f); colors[ImGuiCol_NavWindowingDimBg] = ImVec4(0.80f, 0.80f, 0.80f, 0.20f); - colors[ImGuiCol_ModalWindowDimBg] = ImVec4(0.80f, 0.80f, 0.80f, 0.35f); + colors[ImGuiCol_ModalWindowDimBg] = ImVec4(0.00f, 0.00f, 0.00f, 0.60f); } diff --git a/src/dusk/imgui/ImGuiFirstRunPreset.cpp b/src/dusk/imgui/ImGuiFirstRunPreset.cpp new file mode 100644 index 0000000000..275af7a0fc --- /dev/null +++ b/src/dusk/imgui/ImGuiFirstRunPreset.cpp @@ -0,0 +1,130 @@ +#include "ImGuiFirstRunPreset.hpp" + +#include "imgui.h" +#include "ImGuiConsole.hpp" +#include "ImGuiEngine.hpp" +#include "dusk/settings.h" +#include "dusk/config.hpp" + +namespace dusk { + +static void ApplyPresetVanilla() { + auto& s = getSettings(); + // todo: lock aspect ratio +} + +static void ApplyPresetDefault() { + auto& s = getSettings(); + // todo: unlock aspect ratio + // todo: instant saving + s.game.hideTvSettingsScreen.setValue(true); +} + +static void ApplyPresetQoL() { + auto& s = getSettings(); + // todo: unlock aspect ratio + // todo: instant saving + // todo: more save files + // todo: autosave + s.game.enableQuickTransform.setValue(true); + s.game.hideTvSettingsScreen.setValue(true); + s.game.noReturnRupees.setValue(true); + s.game.disableRupeeCutscenes.setValue(true); + s.game.noSwordRecoil.setValue(true); + s.game.fastClimbing.setValue(true); + s.game.noMissClimbing.setValue(true); + s.game.fastTears.setValue(true); + s.game.noLowHpSound.setValue(true); + s.game.midnasLamentNonStop.setValue(true); + s.game.enableFastIronBoots.setValue(true); + s.game.canTransformAnywhere.setValue(true); + s.game.enableTurboKeybind.setValue(true); +} + +// ========================================================================= + +void ImGuiFirstRunPreset::draw() { + const char* modalTitle = "Welcome to Dusk!"; + + if (m_done) return; + + if (!m_opened) { + ImGui::OpenPopup(modalTitle); + m_opened = true; + } + + const ImGuiViewport* viewport = ImGui::GetMainViewport(); + ImGui::SetNextWindowPos(viewport->GetCenter(), ImGuiCond_Always, ImVec2(0.5f, 0.5f)); + ImGui::SetNextWindowSize(ImVec2(800.0f * ImGuiScale(), 0.0f), ImGuiCond_Always); + + if (!ImGui::BeginPopupModal(modalTitle, nullptr, ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoMove)) { + // force the user to actually pick one, and not just hit escape to skip the dialog + m_opened = false; + return; + } + + ImGui::TextWrapped("Choose a preset to get started. You can change any setting later from the Enhancements menu."); + ImGui::Spacing(); + ImGui::Separator(); + ImGui::Spacing(); + + int chosen = -1; + + if (ImGui::BeginTable("##presets", 5, ImGuiTableFlags_None)) { + ImGui::TableSetupColumn(nullptr, ImGuiTableColumnFlags_WidthStretch); + ImGui::TableSetupColumn(nullptr, ImGuiTableColumnFlags_WidthFixed, 16.0f * ImGuiScale()); + ImGui::TableSetupColumn(nullptr, ImGuiTableColumnFlags_WidthStretch); + ImGui::TableSetupColumn(nullptr, ImGuiTableColumnFlags_WidthFixed, 16.0f * ImGuiScale()); + ImGui::TableSetupColumn(nullptr, ImGuiTableColumnFlags_WidthStretch); + + ImGui::TableNextRow(); + + ImGui::PushFont(ImGuiEngine::fontLarge); + + ImGui::TableSetColumnIndex(0); + if (ImGui::Button("Vanilla##btn", ImVec2(ImGui::GetContentRegionAvail().x, 80.0f * ImGuiScale()))) { + chosen = 0; + } + + ImGui::TableSetColumnIndex(2); + if (ImGui::Button("Default##btn", ImVec2(ImGui::GetContentRegionAvail().x, 80.0f * ImGuiScale()))) { + chosen = 1; + } + + ImGui::TableSetColumnIndex(4); + if (ImGui::Button("Quality of Life##btn", ImVec2(ImGui::GetContentRegionAvail().x, 80.0f * ImGuiScale()))) { + chosen = 2; + } + + ImGui::PopFont(); + + ImGui::TableNextRow(); + + ImGui::TableSetColumnIndex(0); + ImGui::Spacing(); + ImGui::TextWrapped("All enhancements disabled. Plays closest to the original game; good for speedrunning or simple nostalgia!"); + + ImGui::TableSetColumnIndex(2); + ImGui::Spacing(); + ImGui::TextWrapped("Some enhancements enabled, maintaining a vanilla feel. A good starting point for most players!"); + + ImGui::TableSetColumnIndex(4); + ImGui::Spacing(); + ImGui::TextWrapped("Many quality of life enhancements enabled. Good for seasoned players!"); + + ImGui::EndTable(); + } + + if (chosen >= 0) { + if (chosen == 0) ApplyPresetVanilla(); + if (chosen == 1) ApplyPresetDefault(); + if (chosen == 2) ApplyPresetQoL(); + config::Save(); + m_done = true; + ImGui::CloseCurrentPopup(); + } + + ImGui::EndPopup(); +} + +} // namespace dusk diff --git a/src/dusk/imgui/ImGuiFirstRunPreset.hpp b/src/dusk/imgui/ImGuiFirstRunPreset.hpp new file mode 100644 index 0000000000..33ba6b3d0f --- /dev/null +++ b/src/dusk/imgui/ImGuiFirstRunPreset.hpp @@ -0,0 +1,14 @@ +#pragma once + +namespace dusk { + +class ImGuiFirstRunPreset { +public: + void draw(); + +private: + bool m_opened = false; + bool m_done = false; +}; + +} // namespace dusk From f0fad6280db1c2877fde404a14217ec2cc4797af Mon Sep 17 00:00:00 2001 From: madeline Date: Mon, 6 Apr 2026 00:37:17 -0700 Subject: [PATCH 04/19] turn off turbo keybind by default --- src/dusk/imgui/ImGuiFirstRunPreset.cpp | 1 - src/dusk/settings.cpp | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/dusk/imgui/ImGuiFirstRunPreset.cpp b/src/dusk/imgui/ImGuiFirstRunPreset.cpp index 275af7a0fc..6ac6791455 100644 --- a/src/dusk/imgui/ImGuiFirstRunPreset.cpp +++ b/src/dusk/imgui/ImGuiFirstRunPreset.cpp @@ -38,7 +38,6 @@ static void ApplyPresetQoL() { s.game.midnasLamentNonStop.setValue(true); s.game.enableFastIronBoots.setValue(true); s.game.canTransformAnywhere.setValue(true); - s.game.enableTurboKeybind.setValue(true); } // ========================================================================= diff --git a/src/dusk/settings.cpp b/src/dusk/settings.cpp index 278f783dbf..6a9887c27f 100644 --- a/src/dusk/settings.cpp +++ b/src/dusk/settings.cpp @@ -51,7 +51,7 @@ UserSettings g_userSettings = { .restoreWiiGlitches {"game.restoreWiiGlitches", false}, // Controls - .enableTurboKeybind {"game.enableTurboKeybind", true}, + .enableTurboKeybind {"game.enableTurboKeybind", false}, }, }; From 411ca7e3a2170d98c1df3dba38fc7d699138778d Mon Sep 17 00:00:00 2001 From: MelonSpeedruns Date: Mon, 6 Apr 2026 13:14:14 -0400 Subject: [PATCH 05/19] First time setup preset menu with transition & cool sfx --- include/dusk/config.hpp | 2 +- libs/JSystem/src/JAudio2/JAIStream.cpp | 10 ++++++++++ src/d/actor/d_a_title.cpp | 16 ++++++++++++++++ src/d/d_s_play.cpp | 19 +++++++++++++++++++ src/dusk/config.cpp | 4 +++- src/dusk/imgui/ImGuiConsole.cpp | 9 +++++---- 6 files changed, 54 insertions(+), 6 deletions(-) diff --git a/include/dusk/config.hpp b/include/dusk/config.hpp index 0fd0ad8556..ace11cb424 100644 --- a/include/dusk/config.hpp +++ b/include/dusk/config.hpp @@ -116,7 +116,7 @@ ConfigVarBase* GetConfigVar(std::string_view name); * * For detect first run to prompt the user to choose a preset. */ -bool WasConfigFileMissing(); +bool IsConfigFileMissing(); template const ConfigImplBase* GetConfigImpl() { diff --git a/libs/JSystem/src/JAudio2/JAIStream.cpp b/libs/JSystem/src/JAudio2/JAIStream.cpp index 5799080b12..4c91b1a34a 100644 --- a/libs/JSystem/src/JAudio2/JAIStream.cpp +++ b/libs/JSystem/src/JAudio2/JAIStream.cpp @@ -6,6 +6,10 @@ #include "JSystem/JAudio2/JAIStreamDataMgr.h" #include "JSystem/JAudio2/JAIAudience.h" +#if TARGET_PC +#include +#endif + static void JAIStream_JASAramStreamCallback_(u32 type, JASAramStream* aramStream, void* userData) { JAIStream* stream = (JAIStream*)userData; @@ -35,6 +39,12 @@ JAIStream::JAIStream(JAIStreamMgr* streamMgr, JAISoundStrategyMgr* so void JAIStream::JAIStreamMgr_startID_(JAISoundID id, s32 streamFileEntry, const JGeometry::TVec3* posPtr, JAIAudience* audience, int category) { + #if TARGET_PC + if (dusk::config::IsConfigFileMissing()) { + return; + } + #endif + field_0x298 = category; field_0x294 = streamFileEntry; start_JAISound_(id, posPtr, audience); diff --git a/src/d/actor/d_a_title.cpp b/src/d/actor/d_a_title.cpp index 88ce62ec5c..dad25c74ce 100644 --- a/src/d/actor/d_a_title.cpp +++ b/src/d/actor/d_a_title.cpp @@ -18,6 +18,10 @@ #include "JSystem/J2DGraph/J2DTextBox.h" #include "m_Do/m_Do_graphic.h" +#if TARGET_PC +#include +#endif + class daTit_HIO_c : public JORReflexible { public: daTit_HIO_c(); @@ -152,6 +156,12 @@ int daTitle_c::createHeapCallBack(fopAc_ac_c* actor) { } int daTitle_c::Execute() { + #if TARGET_PC + if (dusk::config::IsConfigFileMissing()) { + return 0; + } + #endif + #if PLATFORM_WII || PLATFORM_SHIELD mDoGph_gInf_c::resetDimming(); #endif @@ -364,6 +374,12 @@ int daTitle_c::getDemoPrm() { } int daTitle_c::Draw() { + #if TARGET_PC + if (dusk::config::IsConfigFileMissing()) { + return 0; + } + #endif + J3DModelData* modelData = mpModel->getModelData(); cMtx_trans(mpModel->getBaseTRMtx(), IREG_F(7), IREG_F(8), IREG_F(9) + -430.0f); mpModel->getBaseScale()->x = -1.0f; diff --git a/src/d/d_s_play.cpp b/src/d/d_s_play.cpp index 3ef8ea7eb2..6dff510bc6 100644 --- a/src/d/d_s_play.cpp +++ b/src/d/d_s_play.cpp @@ -39,7 +39,10 @@ #include "JSystem/JKernel/JKRAram.h" #include "JSystem/JKernel/JKRAramArchive.h" +#if TARGET_PC #include "dusk/memory.h" +#include +#endif #if DEBUG #include "d/d_s_menu.h" @@ -698,6 +701,10 @@ static u8 lbl_8074CAE4; static u32 l_sceneChangeStartTick; #endif +#if TARGET_PC +BOOL firstTime = FALSE; +#endif + static int dScnPly_Execute(dScnPly_c* i_this) { #if DEBUG fapGm_HIO_c::startCpuTimer(); @@ -778,7 +785,19 @@ static int dScnPly_Execute(dScnPly_c* i_this) { dJprev_c::get()->update(); #endif + #if TARGET_PC + if (dusk::config::IsConfigFileMissing()) { + firstTime = true; + } else if (firstTime) { + mDoAud_seStart(Z2SE_TITLE_ENTER, NULL, 0, 0); + JUTGamePad::C3ButtonReset::sResetSwitchPushing = true; + firstTime = false; + } else { + dDemo_c::update(); + } + #else dDemo_c::update(); + #endif #if DEBUG dJcame_c::get()->update(); diff --git a/src/dusk/config.cpp b/src/dusk/config.cpp index 1fa2e689b6..45089ed726 100644 --- a/src/dusk/config.cpp +++ b/src/dusk/config.cpp @@ -212,9 +212,11 @@ void dusk::config::Save() { } io::FileStream::WriteAllText(configJsonPath.c_str(), j.dump(4)); + + s_configFileMissing = false; } -bool dusk::config::WasConfigFileMissing() { +bool dusk::config::IsConfigFileMissing() { return s_configFileMissing; } diff --git a/src/dusk/imgui/ImGuiConsole.cpp b/src/dusk/imgui/ImGuiConsole.cpp index 7140af5fc7..5b025e15ab 100644 --- a/src/dusk/imgui/ImGuiConsole.cpp +++ b/src/dusk/imgui/ImGuiConsole.cpp @@ -179,15 +179,16 @@ namespace dusk { ImGuiConsole::ImGuiConsole() {} void ImGuiConsole::PreDraw() { + if (config::IsConfigFileMissing()) { + m_firstRunPreset.draw(); + return; + } + if (!m_isLaunchInitialized) { m_toasts.emplace_back("Press F1 to toggle menu"s, 5.f); m_isLaunchInitialized = true; } - if (config::WasConfigFileMissing()) { - m_firstRunPreset.draw(); - } - getTransientSettings().skipFrameRateLimit = getSettings().game.enableTurboKeybind && ImGui::IsKeyDown(ImGuiKey_Tab); if ((ImGui::IsKeyDown(ImGuiKey_LeftCtrl) || ImGui::IsKeyDown(ImGuiKey_RightCtrl)) && From 5865ccc9d5bc02a3ed94cc48c27b43c32e74a2af Mon Sep 17 00:00:00 2001 From: Pheenoh Date: Sat, 11 Apr 2026 14:43:08 -0600 Subject: [PATCH 06/19] Add Collection tab and Inventory slot defaults to save editor --- src/dusk/imgui/ImGuiSaveEditor.cpp | 507 ++++++++++++++++++++++++++++- src/dusk/imgui/ImGuiSaveEditor.hpp | 1 + 2 files changed, 507 insertions(+), 1 deletion(-) diff --git a/src/dusk/imgui/ImGuiSaveEditor.cpp b/src/dusk/imgui/ImGuiSaveEditor.cpp index e461545e33..b840646206 100644 --- a/src/dusk/imgui/ImGuiSaveEditor.cpp +++ b/src/dusk/imgui/ImGuiSaveEditor.cpp @@ -7,6 +7,8 @@ #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 @@ -280,6 +282,132 @@ namespace dusk { { dItemNo_NONE_e, {"None"} }, }; + static constexpr int BUG_SPECIES_COUNT = 12; + + static const u8 sBugItemIds[BUG_SPECIES_COUNT * 2] = { + dItemNo_M_ANT_e, dItemNo_F_ANT_e, + dItemNo_M_MAYFLY_e, dItemNo_F_MAYFLY_e, + dItemNo_M_BEETLE_e, dItemNo_F_BEETLE_e, + dItemNo_M_MANTIS_e, dItemNo_F_MANTIS_e, + dItemNo_M_STAG_BEETLE_e, dItemNo_F_STAG_BEETLE_e, + dItemNo_M_DANGOMUSHI_e, dItemNo_F_DANGOMUSHI_e, + dItemNo_M_BUTTERFLY_e, dItemNo_F_BUTTERFLY_e, + dItemNo_M_LADYBUG_e, dItemNo_F_LADYBUG_e, + dItemNo_M_SNAIL_e, dItemNo_F_SNAIL_e, + dItemNo_M_NANAFUSHI_e, dItemNo_F_NANAFUSHI_e, + dItemNo_M_GRASSHOPPER_e, dItemNo_F_GRASSHOPPER_e, + dItemNo_M_DRAGONFLY_e, dItemNo_F_DRAGONFLY_e, + }; + + static const u16 sBugTurnInFlags[BUG_SPECIES_COUNT * 2] = { + dSv_event_flag_c::F_0421, dSv_event_flag_c::F_0422, + dSv_event_flag_c::F_0423, dSv_event_flag_c::F_0424, + dSv_event_flag_c::F_0401, dSv_event_flag_c::F_0402, + dSv_event_flag_c::F_0413, dSv_event_flag_c::F_0414, + dSv_event_flag_c::F_0405, dSv_event_flag_c::F_0406, + dSv_event_flag_c::F_0411, dSv_event_flag_c::F_0412, + dSv_event_flag_c::F_0403, dSv_event_flag_c::F_0404, + dSv_event_flag_c::F_0415, dSv_event_flag_c::F_0416, + dSv_event_flag_c::F_0417, dSv_event_flag_c::F_0418, + dSv_event_flag_c::F_0409, dSv_event_flag_c::F_0410, + dSv_event_flag_c::F_0407, dSv_event_flag_c::F_0408, + dSv_event_flag_c::F_0419, dSv_event_flag_c::F_0420, + }; + + static const char* sBugSpeciesNames[BUG_SPECIES_COUNT] = { + "Ant", "Dayfly", "Beetle", "Mantis", + "Stag Beetle", "Pill Bug", "Butterfly", "Ladybug", + "Snail", "Phasmid", "Grasshopper", "Dragonfly", + }; + + static constexpr int HIDDEN_SKILL_COUNT = 7; + + static const u16 sHiddenSkillFlags[HIDDEN_SKILL_COUNT] = { + dSv_event_flag_c::F_0339, dSv_event_flag_c::F_0338, + dSv_event_flag_c::F_0340, dSv_event_flag_c::F_0341, + dSv_event_flag_c::F_0342, dSv_event_flag_c::F_0343, + dSv_event_flag_c::F_0344, + }; + + static const char* sHiddenSkillNames[HIDDEN_SKILL_COUNT] = { + "Ending Blow", "Shield Attack", "Back Slice", "Helm Splitter", + "Mortal Draw", "Jump Strike", "Great Spin", + }; + + static constexpr int LETTER_COUNT = 16; + + static const char* sLetterSenders[LETTER_COUNT] = { + "Renado", "Ooccoo 1", "Ooccoo 2", "The Postman", + "Kakariko Goods", "Barnes 1", "Barnes 2", "Barnes Bombs", + "Malo Mart", "Telma", "Purlo", "From Jr.", + "Princess Agitha", "Lanayru Tourism", "Shad", "Yeta", + }; + + static constexpr int FISH_COUNT = 6; + + static const struct { + u8 index; + const char* name; + } sFishSpecies[FISH_COUNT] = { + { 3, "Ordon Catfish" }, + { 5, "Greengill" }, + { 4, "Reekfish" }, + { 0, "Hyrule Bass" }, + { 2, "Hylian Pike" }, + { 1, "Hylian Loach" }, + }; + + static const char* sSwordNames[4] = { + "Ordon Sword", "Master Sword", "Wooden Sword", "Light Sword", + }; + + static const char* sShieldNames[3] = { + "Wooden Shield", "Ordon Shield", "Hylian Shield", + }; + + static const char* sFusedShadowNames[3] = { + "Forest Temple", + "Goron Mines", + "Lakebed Temple", + }; + + static const struct { + u8 index; + const char* name; + } sMirrorShards[3] = { + { 1, "Snowpeak Ruins" }, + { 2, "Temple of Time" }, + { 3, "City in the Sky" }, + }; + + static const struct { + u8 slot; + u8 item; + } sDefaultInventory[] = { + { SLOT_0, dItemNo_BOOMERANG_e }, + { SLOT_1, dItemNo_KANTERA_e }, + { SLOT_2, dItemNo_SPINNER_e }, + { SLOT_3, dItemNo_HVY_BOOTS_e }, + { SLOT_4, dItemNo_BOW_e }, + { SLOT_5, dItemNo_HAWK_EYE_e }, + { SLOT_6, dItemNo_IRONBALL_e }, + { SLOT_8, dItemNo_COPY_ROD_e }, + { SLOT_9, dItemNo_HOOKSHOT_e }, + { SLOT_10, dItemNo_W_HOOKSHOT_e }, + { SLOT_11, dItemNo_EMPTY_BOTTLE_e }, + { SLOT_12, dItemNo_EMPTY_BOTTLE_e }, + { SLOT_13, dItemNo_EMPTY_BOTTLE_e }, + { SLOT_14, dItemNo_EMPTY_BOTTLE_e }, + { SLOT_15, dItemNo_NORMAL_BOMB_e }, + { SLOT_16, dItemNo_WATER_BOMB_e }, + { SLOT_17, dItemNo_POKE_BOMB_e }, + { SLOT_18, dItemNo_DUNGEON_EXIT_e }, + { SLOT_20, dItemNo_FISHING_ROD_1_e}, + { SLOT_21, dItemNo_HORSE_FLUTE_e }, + { SLOT_22, dItemNo_ANCIENT_DOCUMENT_e }, + { SLOT_23, dItemNo_PACHINKO_e }, + }; + ImGuiSaveEditor::ImGuiSaveEditor() {} void ImGuiSaveEditor::draw(bool& open) { @@ -307,7 +435,7 @@ namespace dusk { } if (ImGui::BeginTabItem("Collection")) { - //DrawFlagsTab(); + drawCollectionTab(); ImGui::EndTabItem(); } @@ -675,9 +803,30 @@ namespace dusk { } } + static u8 getSlotDefault(int slot) { + for (size_t i = 0; i < sizeof(sDefaultInventory) / sizeof(sDefaultInventory[0]); i++) { + if (sDefaultInventory[i].slot == slot) { + return sDefaultInventory[i].item; + } + } + return dItemNo_NONE_e; + } + void ImGuiSaveEditor::drawInventoryTab() { dSv_player_item_c& item = dComIfGs_getSaveData()->getPlayer().getItem(); + if (ImGui::Button("Default All##inv_default_all")) { + for (int slot = 0; slot < 24; slot++) { + dComIfGs_setItem(slot, getSlotDefault(slot)); + } + } + ImGui::SameLine(); + if (ImGui::Button("Clear All##inv_clear_all")) { + for (int slot = 0; slot < 24; slot++) { + dComIfGs_setItem(slot, dItemNo_NONE_e); + } + } + ImGuiBeginGroupPanel("Items", { 200, 100 }); for (int slot = 0; slot < 24; slot++) { ImGui::Text("Slot %02d: ", slot); @@ -696,12 +845,354 @@ namespace dusk { } ImGui::EndCombo(); } + ImGui::SameLine(); + if (ImGui::SmallButton(fmt::format("Default##slot_d_{}", slot).c_str())) { + dComIfGs_setItem(slot, getSlotDefault(slot)); + } + ImGui::SameLine(); + if (ImGui::SmallButton(fmt::format("Clear##slot_c_{}", slot).c_str())) { + dComIfGs_setItem(slot, dItemNo_NONE_e); + } } ImGuiEndGroupPanel(); } + static inline void setItemFirstBit(u8 itemNo, bool owned) { + if (owned) { + dComIfGs_onItemFirstBit(itemNo); + } else { + dComIfGs_offItemFirstBit(itemNo); + } + } + + static inline void setEventBit(u16 flag, bool on) { + if (on) { + dComIfGs_onEventBit(flag); + } else { + dComIfGs_offEventBit(flag); + } + } + + static void setLetterGetFlag(int idx, bool received) { + dSv_letter_info_c& info = dComIfGs_getSaveData()->getPlayer().getLetterInfo(); + if (received) { + if (dComIfGs_isLetterGetFlag(idx)) return; + dComIfGs_onLetterGetFlag(idx); + u8 slot = dMeter2Info_getRecieveLetterNum() - 1; + if (slot < 64) { + dComIfGs_setGetNumber(slot, (u8)(idx + 1)); + } + } else { + if (!dComIfGs_isLetterGetFlag(idx)) return; + info.mLetterGetFlags[idx >> 5] &= ~(1u << (idx & 0x1F)); + for (int j = 0; j < 64; j++) { + if (dComIfGs_getGetNumber(j) == idx + 1) { + for (int k = j; k < 63; k++) { + dComIfGs_setGetNumber(k, dComIfGs_getGetNumber(k + 1)); + } + dComIfGs_setGetNumber(63, 0); + break; + } + } + } + } + + void ImGuiSaveEditor::drawCollectionTab() { + ImGuiBeginGroupPanel("Swords", { 240, 0 }); + for (int i = 0; i < 4; i++) { + bool got = dComIfGs_isCollectSword((u8)i) != 0; + if (ImGui::Checkbox(fmt::format("{0}##sword_{1}", sSwordNames[i], i).c_str(), &got)) { + if (got) dComIfGs_setCollectSword((u8)i); + else dComIfGs_offCollectSword((u8)i); + } + } + ImGuiEndGroupPanel(); + + ImGuiBeginGroupPanel("Shields", { 240, 0 }); + for (int i = 0; i < 3; i++) { + bool got = dComIfGs_isCollectShield((u8)i) != 0; + if (ImGui::Checkbox(fmt::format("{0}##shield_{1}", sShieldNames[i], i).c_str(), &got)) { + if (got) dComIfGs_setCollectShield((u8)i); + else dComIfGs_offCollectShield((u8)i); + } + } + ImGuiEndGroupPanel(); + + ImGuiBeginGroupPanel("Tunics", { 240, 0 }); + { + bool ordonClothes = dComIfGs_isItemFirstBit(dItemNo_WEAR_CASUAL_e) != 0; + if (ImGui::Checkbox("Ordon Clothes##tunic_ordon", &ordonClothes)) { + setItemFirstBit(dItemNo_WEAR_CASUAL_e, ordonClothes); + } + + bool greenTunic = dComIfGs_isCollectClothes(KOKIRI_CLOTHES_FLAG) != 0; + if (ImGui::Checkbox("Hero's Clothes##tunic_green", &greenTunic)) { + if (greenTunic) dComIfGs_setCollectClothes(KOKIRI_CLOTHES_FLAG); + else dComIfGs_offCollectClothes(KOKIRI_CLOTHES_FLAG); + } + + bool zoraArmor = dComIfGs_isItemFirstBit(dItemNo_WEAR_ZORA_e) != 0; + if (ImGui::Checkbox("Zora Armor##tunic_zora", &zoraArmor)) { + setItemFirstBit(dItemNo_WEAR_ZORA_e, zoraArmor); + } + + bool magicArmor = dComIfGs_isItemFirstBit(dItemNo_ARMOR_e) != 0; + if (ImGui::Checkbox("Magic Armor##tunic_magic", &magicArmor)) { + setItemFirstBit(dItemNo_ARMOR_e, magicArmor); + } + } + ImGuiEndGroupPanel(); + + ImGuiBeginGroupPanel("Fused Shadows", { 240, 0 }); + for (int i = 0; i < 3; i++) { + bool got = dComIfGs_isCollectCrystal((u8)i) != 0; + if (ImGui::Checkbox( + fmt::format("{0}##fs_{1}", sFusedShadowNames[i], i).c_str(), &got)) { + if (got) dComIfGs_onCollectCrystal((u8)i); + else dComIfGs_offCollectCrystal((u8)i); + } + } + ImGui::Spacing(); + if (ImGui::Button("All##fs_all")) { + for (int i = 0; i < 3; i++) dComIfGs_onCollectCrystal((u8)i); + } + ImGui::SameLine(); + if (ImGui::Button("None##fs_clear")) { + for (int i = 0; i < 3; i++) dComIfGs_offCollectCrystal((u8)i); + } + ImGuiEndGroupPanel(); + + ImGuiBeginGroupPanel("Mirror Shards", { 240, 0 }); + for (int i = 0; i < 3; i++) { + u8 idx = sMirrorShards[i].index; + bool got = dComIfGs_isCollectMirror(idx) != 0; + if (ImGui::Checkbox( + fmt::format("{0}##ms_{1}", sMirrorShards[i].name, i).c_str(), &got)) { + if (got) dComIfGs_onCollectMirror(idx); + else dComIfGs_offCollectMirror(idx); + } + } + ImGui::Spacing(); + if (ImGui::Button("All##ms_all")) { + for (int i = 0; i < 3; i++) dComIfGs_onCollectMirror(sMirrorShards[i].index); + } + ImGui::SameLine(); + if (ImGui::Button("None##ms_clear")) { + for (int i = 0; i < 3; i++) dComIfGs_offCollectMirror(sMirrorShards[i].index); + } + ImGuiEndGroupPanel(); + + ImGuiBeginGroupPanel("Poe Souls", { 240, 0 }); + int poeCount = dComIfGs_getPohSpiritNum(); + ImGui::Text("Collected:"); + ImGui::SameLine(); + ImGui::SetNextItemWidth(100.0f); + if (ImGui::InputInt("##poe_count", &poeCount)) { + if (poeCount < 0) poeCount = 0; + if (poeCount > 60) poeCount = 60; + dComIfGs_setPohSpiritNum((u8)poeCount); + } + ImGui::SameLine(); + ImGui::TextDisabled("/ 60"); + ImGui::Spacing(); + if (ImGui::Button("All 60##poe_all")) { + dComIfGs_setPohSpiritNum(60); + } + ImGui::SameLine(); + if (ImGui::Button("Clear##poe_clear")) { + dComIfGs_setPohSpiritNum(0); + } + ImGuiEndGroupPanel(); + + ImGuiBeginGroupPanel("Golden Bugs", { 360, 0 }); + + if (ImGui::BeginTable("GoldenBugTable", 5, + ImGuiTableFlags_SizingFixedFit | ImGuiTableFlags_RowBg)) { + ImGui::TableSetupColumn("Species"); + ImGui::TableSetupColumn("Male"); + ImGui::TableSetupColumn("Female"); + ImGui::TableSetupColumn("M \xe2\x86\x92 Agitha"); // M → Agitha + ImGui::TableSetupColumn("F \xe2\x86\x92 Agitha"); // F → Agitha + ImGui::TableHeadersRow(); + + for (int species = 0; species < BUG_SPECIES_COUNT; species++) { + int maleIdx = species * 2; + int femaleIdx = species * 2 + 1; + u8 maleItem = sBugItemIds[maleIdx]; + u8 femaleItem = sBugItemIds[femaleIdx]; + u16 maleFlag = sBugTurnInFlags[maleIdx]; + u16 femaleFlag = sBugTurnInFlags[femaleIdx]; + + ImGui::TableNextRow(); + + ImGui::TableSetColumnIndex(0); + ImGui::TextUnformatted(sBugSpeciesNames[species]); + + ImGui::TableSetColumnIndex(1); + bool maleOwned = dComIfGs_isItemFirstBit(maleItem) != 0; + if (ImGui::Checkbox(fmt::format("##bugM_own_{}", species).c_str(), &maleOwned)) { + setItemFirstBit(maleItem, maleOwned); + } + + ImGui::TableSetColumnIndex(2); + bool femaleOwned = dComIfGs_isItemFirstBit(femaleItem) != 0; + if (ImGui::Checkbox(fmt::format("##bugF_own_{}", species).c_str(), &femaleOwned)) { + setItemFirstBit(femaleItem, femaleOwned); + } + + ImGui::TableSetColumnIndex(3); + bool maleGiven = dComIfGs_isEventBit(maleFlag) != 0; + if (ImGui::Checkbox(fmt::format("##bugM_giv_{}", species).c_str(), &maleGiven)) { + setEventBit(maleFlag, maleGiven); + } + + ImGui::TableSetColumnIndex(4); + bool femaleGiven = dComIfGs_isEventBit(femaleFlag) != 0; + if (ImGui::Checkbox(fmt::format("##bugF_giv_{}", species).c_str(), &femaleGiven)) { + setEventBit(femaleFlag, femaleGiven); + } + } + + ImGui::EndTable(); + } + + ImGui::Spacing(); + if (ImGui::Button("Collect All##bugs_all")) { + for (int i = 0; i < BUG_SPECIES_COUNT * 2; i++) { + dComIfGs_onItemFirstBit(sBugItemIds[i]); + } + } + ImGui::SameLine(); + if (ImGui::Button("Clear All##bugs_clear")) { + for (int i = 0; i < BUG_SPECIES_COUNT * 2; i++) { + dComIfGs_offItemFirstBit(sBugItemIds[i]); + dComIfGs_offEventBit(sBugTurnInFlags[i]); + } + } + ImGui::SameLine(); + if (ImGui::Button("Give All to Agitha##bugs_giveall")) { + for (int i = 0; i < BUG_SPECIES_COUNT * 2; i++) { + dComIfGs_onEventBit(sBugTurnInFlags[i]); + } + } + + ImGuiEndGroupPanel(); + + ImGuiBeginGroupPanel("Hidden Skills", { 240, 0 }); + for (int i = 0; i < HIDDEN_SKILL_COUNT; i++) { + bool learned = dComIfGs_isEventBit(sHiddenSkillFlags[i]) != 0; + if (ImGui::Checkbox( + fmt::format("{0}##skill_{1}", sHiddenSkillNames[i], i).c_str(), &learned)) { + setEventBit(sHiddenSkillFlags[i], learned); + } + } + ImGui::Spacing(); + if (ImGui::Button("Learn All##skills_all")) { + for (int i = 0; i < HIDDEN_SKILL_COUNT; i++) { + dComIfGs_onEventBit(sHiddenSkillFlags[i]); + } + } + ImGui::SameLine(); + if (ImGui::Button("Forget All##skills_clear")) { + for (int i = 0; i < HIDDEN_SKILL_COUNT; i++) { + dComIfGs_offEventBit(sHiddenSkillFlags[i]); + } + } + ImGuiEndGroupPanel(); + + ImGuiBeginGroupPanel("Postman Letters", { 240, 0 }); + for (int i = 0; i < LETTER_COUNT; i++) { + bool had = dComIfGs_isLetterGetFlag(i) != 0; + if (ImGui::Checkbox( + fmt::format("{0}##letter_{1}", sLetterSenders[i], i).c_str(), &had)) { + setLetterGetFlag(i, had); + } + } + ImGui::Spacing(); + if (ImGui::Button("Receive All##letters_all")) { + for (int i = 0; i < LETTER_COUNT; i++) setLetterGetFlag(i, true); + } + ImGui::SameLine(); + if (ImGui::Button("Clear All##letters_clear")) { + for (int i = 0; i < LETTER_COUNT; i++) setLetterGetFlag(i, false); + } + ImGuiEndGroupPanel(); + + ImGuiBeginGroupPanel("Fishing Log", { 360, 0 }); + { + dSv_fishing_info_c& fish = dComIfGs_getSaveData()->getPlayer().getFishingInfo(); + if (ImGui::BeginTable("FishTable", 3, + ImGuiTableFlags_SizingFixedFit | ImGuiTableFlags_RowBg)) { + ImGui::TableSetupColumn("Species"); + ImGui::TableSetupColumn("Caught"); + ImGui::TableSetupColumn("Biggest (cm)"); + ImGui::TableHeadersRow(); + + for (int i = 0; i < FISH_COUNT; i++) { + u8 idx = sFishSpecies[i].index; + ImGui::TableNextRow(); + + ImGui::TableSetColumnIndex(0); + ImGui::TextUnformatted(sFishSpecies[i].name); + + ImGui::TableSetColumnIndex(1); + int count = dComIfGs_getFishNum(idx); + ImGui::SetNextItemWidth(100.0f); + if (ImGui::InputInt(fmt::format("##fish_c_{}", i).c_str(), &count, 1, 10)) { + if (count < 0) count = 0; + if (count > 999) count = 999; + fish.mFishCount[idx] = (u16)count; + } + + ImGui::TableSetColumnIndex(2); + int size = dComIfGs_getFishSize(idx); + ImGui::SetNextItemWidth(100.0f); + if (ImGui::InputInt(fmt::format("##fish_s_{}", i).c_str(), &size, 1, 10)) { + if (size < 0) size = 0; + if (size > 255) size = 255; + dComIfGs_setFishSize(idx, (u8)size); + } + } + ImGui::EndTable(); + } + } + ImGuiEndGroupPanel(); + + ImGuiBeginGroupPanel("Heart Pieces", { 360, 0 }); + { + int maxLife = dComIfGs_getMaxLife(); + int hearts = maxLife / 5; + int pieces = maxLife % 5; + ImGui::Text("Max Life:"); + ImGui::SameLine(); + ImGui::SetNextItemWidth(160.0f); + if (ImGui::InputInt("##max_life", &maxLife, 1, 5)) { + if (maxLife < 15) maxLife = 15; + if (maxLife > 100) maxLife = 100; + dComIfGs_setMaxLife((u8)maxLife); + u16 maxHealth = (dComIfGs_getMaxLife() / 5) * 4; + if (dComIfGs_getLife() > maxHealth) { + dComIfGs_setLife(maxHealth); + } + } + ImGui::SameLine(); + ImGui::TextDisabled("(%d hearts + %d pieces)", hearts, pieces); + ImGui::Spacing(); + if (ImGui::Button("3 Hearts##hp_min")) { + dComIfGs_setMaxLife(15); + dComIfGs_setLife(12); + } + ImGui::SameLine(); + if (ImGui::Button("20 Hearts##hp_max")) { + dComIfGs_setMaxLife(100); + dComIfGs_setLife(80); + } + } + ImGuiEndGroupPanel(); + } + void drawFlagList(const char* id, BE(u32)& flags) { u32 tempFlagField = flags; @@ -845,5 +1336,19 @@ namespace dusk { if (ImGui::BeginCombo("Target Type", "Hold")) { ImGui::EndCombo(); } + + static const char* kSoundModeNames[] = { "Mono", "Stereo", "Surround" }; + const char* current = (config.mSoundMode < 3) ? kSoundModeNames[config.mSoundMode] + : "Unknown"; + if (ImGui::BeginCombo("Sound", current)) { + for (u8 i = 0; i < 3; i++) { + bool selected = (config.mSoundMode == i); + if (ImGui::Selectable(kSoundModeNames[i], selected)) { + config.mSoundMode = i; + } + if (selected) ImGui::SetItemDefaultFocus(); + } + ImGui::EndCombo(); + } } } \ No newline at end of file diff --git a/src/dusk/imgui/ImGuiSaveEditor.hpp b/src/dusk/imgui/ImGuiSaveEditor.hpp index 3afc1b8145..4b211311b0 100644 --- a/src/dusk/imgui/ImGuiSaveEditor.hpp +++ b/src/dusk/imgui/ImGuiSaveEditor.hpp @@ -15,6 +15,7 @@ namespace dusk { void drawPlayerStatusTab(); void drawLocationTab(); void drawInventoryTab(); + void drawCollectionTab(); void drawFlagsTab(); void drawConfigTab(); From 3497ddbcd0921e772483edfd962fd962466fb0dd Mon Sep 17 00:00:00 2001 From: MelonSpeedruns Date: Sat, 11 Apr 2026 16:55:55 -0400 Subject: [PATCH 07/19] removed unused variables and includes --- libs/JSystem/src/JAudio2/JAIStream.cpp | 4 ---- src/d/actor/d_a_title.cpp | 2 +- src/d/d_s_play.cpp | 5 ----- 3 files changed, 1 insertion(+), 10 deletions(-) diff --git a/libs/JSystem/src/JAudio2/JAIStream.cpp b/libs/JSystem/src/JAudio2/JAIStream.cpp index 52c9da519d..5799080b12 100644 --- a/libs/JSystem/src/JAudio2/JAIStream.cpp +++ b/libs/JSystem/src/JAudio2/JAIStream.cpp @@ -6,10 +6,6 @@ #include "JSystem/JAudio2/JAIStreamDataMgr.h" #include "JSystem/JAudio2/JAIAudience.h" -#if TARGET_PC -#include -#endif - static void JAIStream_JASAramStreamCallback_(u32 type, JASAramStream* aramStream, void* userData) { JAIStream* stream = (JAIStream*)userData; diff --git a/src/d/actor/d_a_title.cpp b/src/d/actor/d_a_title.cpp index bae2ca7e14..2bf5f0bccb 100644 --- a/src/d/actor/d_a_title.cpp +++ b/src/d/actor/d_a_title.cpp @@ -162,7 +162,7 @@ int daTitle_c::Execute() { } #endif - #if PLATFORM_WII || PLATFORM_SHIELD +#if PLATFORM_WII || PLATFORM_SHIELD mDoGph_gInf_c::resetDimming(); #endif diff --git a/src/d/d_s_play.cpp b/src/d/d_s_play.cpp index 6b494b5bc8..3f3cd073b9 100644 --- a/src/d/d_s_play.cpp +++ b/src/d/d_s_play.cpp @@ -41,7 +41,6 @@ #if TARGET_PC #include "dusk/memory.h" -#include #endif #if DEBUG @@ -701,10 +700,6 @@ static u8 lbl_8074CAE4; static u32 l_sceneChangeStartTick; #endif -#if TARGET_PC -BOOL firstTime = FALSE; -#endif - static int dScnPly_Execute(dScnPly_c* i_this) { #if DEBUG fapGm_HIO_c::startCpuTimer(); From fd59a6e2814f587be9dec0144b3882b9af3cf33b Mon Sep 17 00:00:00 2001 From: MelonSpeedruns Date: Sat, 11 Apr 2026 17:16:40 -0400 Subject: [PATCH 08/19] Only 2 presets for now, Standard and HD --- src/dusk/imgui/ImGuiFirstRunPreset.cpp | 51 +++++++------------------- 1 file changed, 13 insertions(+), 38 deletions(-) diff --git a/src/dusk/imgui/ImGuiFirstRunPreset.cpp b/src/dusk/imgui/ImGuiFirstRunPreset.cpp index 2ab333fbc3..7fa5c11fa6 100644 --- a/src/dusk/imgui/ImGuiFirstRunPreset.cpp +++ b/src/dusk/imgui/ImGuiFirstRunPreset.cpp @@ -8,25 +8,13 @@ namespace dusk { -static void ApplyPresetVanilla() { +static void ApplyPresetStandard() { auto& s = getSettings(); - // todo: lock aspect ratio + s.video.lockAspectRatio.setValue(true); } -static void ApplyPresetDefault() { +static void ApplyPresetHD() { auto& s = getSettings(); - // todo: unlock aspect ratio - // todo: instant saving - s.game.hideTvSettingsScreen.setValue(true); -} - -static void ApplyPresetQoL() { - auto& s = getSettings(); - // todo: unlock aspect ratio - // todo: instant saving - // todo: more save files - // todo: autosave - s.game.enableQuickTransform.setValue(true); s.game.hideTvSettingsScreen.setValue(true); s.game.noReturnRupees.setValue(true); s.game.disableRupeeCutscenes.setValue(true); @@ -34,10 +22,8 @@ static void ApplyPresetQoL() { s.game.fastClimbing.setValue(true); s.game.noMissClimbing.setValue(true); s.game.fastTears.setValue(true); - s.game.noLowHpSound.setValue(true); - s.game.midnasLamentNonStop.setValue(true); - s.game.enableFastIronBoots.setValue(true); - s.game.canTransformAnywhere.setValue(true); + s.game.biggerWallets.setValue(true); + s.game.invertCameraXAxis.setValue(true); } // ========================================================================= @@ -69,55 +55,44 @@ void ImGuiFirstRunPreset::draw() { int chosen = -1; - if (ImGui::BeginTable("##presets", 5, ImGuiTableFlags_None)) { - ImGui::TableSetupColumn(nullptr, ImGuiTableColumnFlags_WidthStretch); - ImGui::TableSetupColumn(nullptr, ImGuiTableColumnFlags_WidthFixed, 16.0f * ImGuiScale()); + if (ImGui::BeginTable("##presets", 3, ImGuiTableFlags_None)) { ImGui::TableSetupColumn(nullptr, ImGuiTableColumnFlags_WidthStretch); ImGui::TableSetupColumn(nullptr, ImGuiTableColumnFlags_WidthFixed, 16.0f * ImGuiScale()); ImGui::TableSetupColumn(nullptr, ImGuiTableColumnFlags_WidthStretch); + ImGui::TableNextRow(); ImGui::PushFont(ImGuiEngine::fontLarge); ImGui::TableSetColumnIndex(0); - if (ImGui::Button("Vanilla##btn", ImVec2(ImGui::GetContentRegionAvail().x, 80.0f * ImGuiScale()))) { + if (ImGui::Button("Standard##btn", ImVec2(ImGui::GetContentRegionAvail().x, 80.0f * ImGuiScale()))) { chosen = 0; } ImGui::TableSetColumnIndex(2); - if (ImGui::Button("Default##btn", ImVec2(ImGui::GetContentRegionAvail().x, 80.0f * ImGuiScale()))) { + if (ImGui::Button("HD##btn", ImVec2(ImGui::GetContentRegionAvail().x, 80.0f * ImGuiScale()))) { chosen = 1; } - ImGui::TableSetColumnIndex(4); - if (ImGui::Button("Quality of Life##btn", ImVec2(ImGui::GetContentRegionAvail().x, 80.0f * ImGuiScale()))) { - chosen = 2; - } - ImGui::PopFont(); ImGui::TableNextRow(); ImGui::TableSetColumnIndex(0); ImGui::Spacing(); - ImGui::TextWrapped("All enhancements disabled. Plays closest to the original game; good for speedrunning or simple nostalgia!"); + ImGui::TextWrapped("All enhancements disabled to match the GameCube version. Good for speedrunning or simple nostalgia!"); ImGui::TableSetColumnIndex(2); ImGui::Spacing(); - ImGui::TextWrapped("Some enhancements enabled, maintaining a vanilla feel. A good starting point for most players!"); - - ImGui::TableSetColumnIndex(4); - ImGui::Spacing(); - ImGui::TextWrapped("Many quality of life enhancements enabled. Good for seasoned players!"); + ImGui::TextWrapped("Some enhancements enabled to match the HD version. A good starting point for most players!"); ImGui::EndTable(); } if (chosen >= 0) { - if (chosen == 0) ApplyPresetVanilla(); - if (chosen == 1) ApplyPresetDefault(); - if (chosen == 2) ApplyPresetQoL(); + if (chosen == 0) ApplyPresetStandard(); + if (chosen == 1) ApplyPresetHD(); getSettings().backend.wasPresetChosen.setValue(true); config::Save(); From be8e67ab18984cb6d95a06d278c826057edba3a1 Mon Sep 17 00:00:00 2001 From: Pheenoh Date: Sat, 11 Apr 2026 15:18:22 -0600 Subject: [PATCH 09/19] Group Collection tab sections into collapsible tree nodes --- src/dusk/imgui/ImGuiSaveEditor.cpp | 505 +++++++++++++++-------------- 1 file changed, 260 insertions(+), 245 deletions(-) diff --git a/src/dusk/imgui/ImGuiSaveEditor.cpp b/src/dusk/imgui/ImGuiSaveEditor.cpp index b840646206..fd45789ccf 100644 --- a/src/dusk/imgui/ImGuiSaveEditor.cpp +++ b/src/dusk/imgui/ImGuiSaveEditor.cpp @@ -900,297 +900,312 @@ namespace dusk { } void ImGuiSaveEditor::drawCollectionTab() { - ImGuiBeginGroupPanel("Swords", { 240, 0 }); - for (int i = 0; i < 4; i++) { - bool got = dComIfGs_isCollectSword((u8)i) != 0; - if (ImGui::Checkbox(fmt::format("{0}##sword_{1}", sSwordNames[i], i).c_str(), &got)) { - if (got) dComIfGs_setCollectSword((u8)i); - else dComIfGs_offCollectSword((u8)i); - } - } - ImGuiEndGroupPanel(); - - ImGuiBeginGroupPanel("Shields", { 240, 0 }); - for (int i = 0; i < 3; i++) { - bool got = dComIfGs_isCollectShield((u8)i) != 0; - if (ImGui::Checkbox(fmt::format("{0}##shield_{1}", sShieldNames[i], i).c_str(), &got)) { - if (got) dComIfGs_setCollectShield((u8)i); - else dComIfGs_offCollectShield((u8)i); - } - } - ImGuiEndGroupPanel(); - - ImGuiBeginGroupPanel("Tunics", { 240, 0 }); - { - bool ordonClothes = dComIfGs_isItemFirstBit(dItemNo_WEAR_CASUAL_e) != 0; - if (ImGui::Checkbox("Ordon Clothes##tunic_ordon", &ordonClothes)) { - setItemFirstBit(dItemNo_WEAR_CASUAL_e, ordonClothes); + if (ImGui::TreeNode("Equipment")) { + if (ImGui::TreeNode("Swords")) { + for (int i = 0; i < 4; i++) { + bool got = dComIfGs_isCollectSword((u8)i) != 0; + if (ImGui::Checkbox(fmt::format("{0}##sword_{1}", sSwordNames[i], i).c_str(), &got)) { + if (got) dComIfGs_setCollectSword((u8)i); + else dComIfGs_offCollectSword((u8)i); + } + } + ImGui::TreePop(); } - bool greenTunic = dComIfGs_isCollectClothes(KOKIRI_CLOTHES_FLAG) != 0; - if (ImGui::Checkbox("Hero's Clothes##tunic_green", &greenTunic)) { - if (greenTunic) dComIfGs_setCollectClothes(KOKIRI_CLOTHES_FLAG); - else dComIfGs_offCollectClothes(KOKIRI_CLOTHES_FLAG); + if (ImGui::TreeNode("Shields")) { + for (int i = 0; i < 3; i++) { + bool got = dComIfGs_isCollectShield((u8)i) != 0; + if (ImGui::Checkbox(fmt::format("{0}##shield_{1}", sShieldNames[i], i).c_str(), &got)) { + if (got) dComIfGs_setCollectShield((u8)i); + else dComIfGs_offCollectShield((u8)i); + } + } + ImGui::TreePop(); } - bool zoraArmor = dComIfGs_isItemFirstBit(dItemNo_WEAR_ZORA_e) != 0; - if (ImGui::Checkbox("Zora Armor##tunic_zora", &zoraArmor)) { - setItemFirstBit(dItemNo_WEAR_ZORA_e, zoraArmor); - } - - bool magicArmor = dComIfGs_isItemFirstBit(dItemNo_ARMOR_e) != 0; - if (ImGui::Checkbox("Magic Armor##tunic_magic", &magicArmor)) { - setItemFirstBit(dItemNo_ARMOR_e, magicArmor); - } - } - ImGuiEndGroupPanel(); - - ImGuiBeginGroupPanel("Fused Shadows", { 240, 0 }); - for (int i = 0; i < 3; i++) { - bool got = dComIfGs_isCollectCrystal((u8)i) != 0; - if (ImGui::Checkbox( - fmt::format("{0}##fs_{1}", sFusedShadowNames[i], i).c_str(), &got)) { - if (got) dComIfGs_onCollectCrystal((u8)i); - else dComIfGs_offCollectCrystal((u8)i); - } - } - ImGui::Spacing(); - if (ImGui::Button("All##fs_all")) { - for (int i = 0; i < 3; i++) dComIfGs_onCollectCrystal((u8)i); - } - ImGui::SameLine(); - if (ImGui::Button("None##fs_clear")) { - for (int i = 0; i < 3; i++) dComIfGs_offCollectCrystal((u8)i); - } - ImGuiEndGroupPanel(); - - ImGuiBeginGroupPanel("Mirror Shards", { 240, 0 }); - for (int i = 0; i < 3; i++) { - u8 idx = sMirrorShards[i].index; - bool got = dComIfGs_isCollectMirror(idx) != 0; - if (ImGui::Checkbox( - fmt::format("{0}##ms_{1}", sMirrorShards[i].name, i).c_str(), &got)) { - if (got) dComIfGs_onCollectMirror(idx); - else dComIfGs_offCollectMirror(idx); - } - } - ImGui::Spacing(); - if (ImGui::Button("All##ms_all")) { - for (int i = 0; i < 3; i++) dComIfGs_onCollectMirror(sMirrorShards[i].index); - } - ImGui::SameLine(); - if (ImGui::Button("None##ms_clear")) { - for (int i = 0; i < 3; i++) dComIfGs_offCollectMirror(sMirrorShards[i].index); - } - ImGuiEndGroupPanel(); - - ImGuiBeginGroupPanel("Poe Souls", { 240, 0 }); - int poeCount = dComIfGs_getPohSpiritNum(); - ImGui::Text("Collected:"); - ImGui::SameLine(); - ImGui::SetNextItemWidth(100.0f); - if (ImGui::InputInt("##poe_count", &poeCount)) { - if (poeCount < 0) poeCount = 0; - if (poeCount > 60) poeCount = 60; - dComIfGs_setPohSpiritNum((u8)poeCount); - } - ImGui::SameLine(); - ImGui::TextDisabled("/ 60"); - ImGui::Spacing(); - if (ImGui::Button("All 60##poe_all")) { - dComIfGs_setPohSpiritNum(60); - } - ImGui::SameLine(); - if (ImGui::Button("Clear##poe_clear")) { - dComIfGs_setPohSpiritNum(0); - } - ImGuiEndGroupPanel(); - - ImGuiBeginGroupPanel("Golden Bugs", { 360, 0 }); - - if (ImGui::BeginTable("GoldenBugTable", 5, - ImGuiTableFlags_SizingFixedFit | ImGuiTableFlags_RowBg)) { - ImGui::TableSetupColumn("Species"); - ImGui::TableSetupColumn("Male"); - ImGui::TableSetupColumn("Female"); - ImGui::TableSetupColumn("M \xe2\x86\x92 Agitha"); // M → Agitha - ImGui::TableSetupColumn("F \xe2\x86\x92 Agitha"); // F → Agitha - ImGui::TableHeadersRow(); - - for (int species = 0; species < BUG_SPECIES_COUNT; species++) { - int maleIdx = species * 2; - int femaleIdx = species * 2 + 1; - u8 maleItem = sBugItemIds[maleIdx]; - u8 femaleItem = sBugItemIds[femaleIdx]; - u16 maleFlag = sBugTurnInFlags[maleIdx]; - u16 femaleFlag = sBugTurnInFlags[femaleIdx]; - - ImGui::TableNextRow(); - - ImGui::TableSetColumnIndex(0); - ImGui::TextUnformatted(sBugSpeciesNames[species]); - - ImGui::TableSetColumnIndex(1); - bool maleOwned = dComIfGs_isItemFirstBit(maleItem) != 0; - if (ImGui::Checkbox(fmt::format("##bugM_own_{}", species).c_str(), &maleOwned)) { - setItemFirstBit(maleItem, maleOwned); + if (ImGui::TreeNode("Tunics")) { + bool ordonClothes = dComIfGs_isItemFirstBit(dItemNo_WEAR_CASUAL_e) != 0; + if (ImGui::Checkbox("Ordon Clothes##tunic_ordon", &ordonClothes)) { + setItemFirstBit(dItemNo_WEAR_CASUAL_e, ordonClothes); } - ImGui::TableSetColumnIndex(2); - bool femaleOwned = dComIfGs_isItemFirstBit(femaleItem) != 0; - if (ImGui::Checkbox(fmt::format("##bugF_own_{}", species).c_str(), &femaleOwned)) { - setItemFirstBit(femaleItem, femaleOwned); + bool greenTunic = dComIfGs_isCollectClothes(KOKIRI_CLOTHES_FLAG) != 0; + if (ImGui::Checkbox("Hero's Clothes##tunic_green", &greenTunic)) { + if (greenTunic) dComIfGs_setCollectClothes(KOKIRI_CLOTHES_FLAG); + else dComIfGs_offCollectClothes(KOKIRI_CLOTHES_FLAG); } - ImGui::TableSetColumnIndex(3); - bool maleGiven = dComIfGs_isEventBit(maleFlag) != 0; - if (ImGui::Checkbox(fmt::format("##bugM_giv_{}", species).c_str(), &maleGiven)) { - setEventBit(maleFlag, maleGiven); + bool zoraArmor = dComIfGs_isItemFirstBit(dItemNo_WEAR_ZORA_e) != 0; + if (ImGui::Checkbox("Zora Armor##tunic_zora", &zoraArmor)) { + setItemFirstBit(dItemNo_WEAR_ZORA_e, zoraArmor); } - ImGui::TableSetColumnIndex(4); - bool femaleGiven = dComIfGs_isEventBit(femaleFlag) != 0; - if (ImGui::Checkbox(fmt::format("##bugF_giv_{}", species).c_str(), &femaleGiven)) { - setEventBit(femaleFlag, femaleGiven); + bool magicArmor = dComIfGs_isItemFirstBit(dItemNo_ARMOR_e) != 0; + if (ImGui::Checkbox("Magic Armor##tunic_magic", &magicArmor)) { + setItemFirstBit(dItemNo_ARMOR_e, magicArmor); } + ImGui::TreePop(); } - - ImGui::EndTable(); + ImGui::TreePop(); } - ImGui::Spacing(); - if (ImGui::Button("Collect All##bugs_all")) { - for (int i = 0; i < BUG_SPECIES_COUNT * 2; i++) { - dComIfGs_onItemFirstBit(sBugItemIds[i]); + if (ImGui::TreeNode("Key Items")) { + if (ImGui::TreeNode("Fused Shadows")) { + for (int i = 0; i < 3; i++) { + bool got = dComIfGs_isCollectCrystal((u8)i) != 0; + if (ImGui::Checkbox( + fmt::format("{0}##fs_{1}", sFusedShadowNames[i], i).c_str(), &got)) { + if (got) dComIfGs_onCollectCrystal((u8)i); + else dComIfGs_offCollectCrystal((u8)i); + } + } + ImGui::Spacing(); + if (ImGui::Button("All##fs_all")) { + for (int i = 0; i < 3; i++) dComIfGs_onCollectCrystal((u8)i); + } + ImGui::SameLine(); + if (ImGui::Button("None##fs_clear")) { + for (int i = 0; i < 3; i++) dComIfGs_offCollectCrystal((u8)i); + } + ImGui::TreePop(); } - } - ImGui::SameLine(); - if (ImGui::Button("Clear All##bugs_clear")) { - for (int i = 0; i < BUG_SPECIES_COUNT * 2; i++) { - dComIfGs_offItemFirstBit(sBugItemIds[i]); - dComIfGs_offEventBit(sBugTurnInFlags[i]); - } - } - ImGui::SameLine(); - if (ImGui::Button("Give All to Agitha##bugs_giveall")) { - for (int i = 0; i < BUG_SPECIES_COUNT * 2; i++) { - dComIfGs_onEventBit(sBugTurnInFlags[i]); + + if (ImGui::TreeNode("Mirror Shards")) { + for (int i = 0; i < 3; i++) { + u8 idx = sMirrorShards[i].index; + bool got = dComIfGs_isCollectMirror(idx) != 0; + if (ImGui::Checkbox( + fmt::format("{0}##ms_{1}", sMirrorShards[i].name, i).c_str(), &got)) { + if (got) dComIfGs_onCollectMirror(idx); + else dComIfGs_offCollectMirror(idx); + } + } + ImGui::Spacing(); + if (ImGui::Button("All##ms_all")) { + for (int i = 0; i < 3; i++) dComIfGs_onCollectMirror(sMirrorShards[i].index); + } + ImGui::SameLine(); + if (ImGui::Button("None##ms_clear")) { + for (int i = 0; i < 3; i++) dComIfGs_offCollectMirror(sMirrorShards[i].index); + } + ImGui::TreePop(); } + ImGui::TreePop(); } - ImGuiEndGroupPanel(); + if (ImGui::TreeNode("Heart Pieces & Poe Souls")) { + if (ImGui::TreeNode("Poe Souls")) { + int poeCount = dComIfGs_getPohSpiritNum(); + ImGui::Text("Collected:"); + ImGui::SameLine(); + ImGui::SetNextItemWidth(100.0f); + if (ImGui::InputInt("##poe_count", &poeCount)) { + if (poeCount < 0) poeCount = 0; + if (poeCount > 60) poeCount = 60; + dComIfGs_setPohSpiritNum((u8)poeCount); + } + ImGui::SameLine(); + ImGui::TextDisabled("/ 60"); + ImGui::Spacing(); + if (ImGui::Button("All 60##poe_all")) { + dComIfGs_setPohSpiritNum(60); + } + ImGui::SameLine(); + if (ImGui::Button("Clear##poe_clear")) { + dComIfGs_setPohSpiritNum(0); + } + ImGui::TreePop(); + } - ImGuiBeginGroupPanel("Hidden Skills", { 240, 0 }); - for (int i = 0; i < HIDDEN_SKILL_COUNT; i++) { - bool learned = dComIfGs_isEventBit(sHiddenSkillFlags[i]) != 0; - if (ImGui::Checkbox( - fmt::format("{0}##skill_{1}", sHiddenSkillNames[i], i).c_str(), &learned)) { - setEventBit(sHiddenSkillFlags[i], learned); + if (ImGui::TreeNode("Heart Pieces")) { + int maxLife = dComIfGs_getMaxLife(); + int hearts = maxLife / 5; + int pieces = maxLife % 5; + ImGui::Text("Max Life:"); + ImGui::SameLine(); + ImGui::SetNextItemWidth(160.0f); + if (ImGui::InputInt("##max_life", &maxLife, 1, 5)) { + if (maxLife < 15) maxLife = 15; + if (maxLife > 100) maxLife = 100; + dComIfGs_setMaxLife((u8)maxLife); + u16 maxHealth = (dComIfGs_getMaxLife() / 5) * 4; + if (dComIfGs_getLife() > maxHealth) { + dComIfGs_setLife(maxHealth); + } + } + ImGui::SameLine(); + ImGui::TextDisabled("(%d hearts + %d pieces)", hearts, pieces); + ImGui::Spacing(); + if (ImGui::Button("3 Hearts##hp_min")) { + dComIfGs_setMaxLife(15); + dComIfGs_setLife(12); + } + ImGui::SameLine(); + if (ImGui::Button("20 Hearts##hp_max")) { + dComIfGs_setMaxLife(100); + dComIfGs_setLife(80); + } + ImGui::TreePop(); } + ImGui::TreePop(); } - ImGui::Spacing(); - if (ImGui::Button("Learn All##skills_all")) { - for (int i = 0; i < HIDDEN_SKILL_COUNT; i++) { - dComIfGs_onEventBit(sHiddenSkillFlags[i]); - } - } - ImGui::SameLine(); - if (ImGui::Button("Forget All##skills_clear")) { - for (int i = 0; i < HIDDEN_SKILL_COUNT; i++) { - dComIfGs_offEventBit(sHiddenSkillFlags[i]); - } - } - ImGuiEndGroupPanel(); - ImGuiBeginGroupPanel("Postman Letters", { 240, 0 }); - for (int i = 0; i < LETTER_COUNT; i++) { - bool had = dComIfGs_isLetterGetFlag(i) != 0; - if (ImGui::Checkbox( - fmt::format("{0}##letter_{1}", sLetterSenders[i], i).c_str(), &had)) { - setLetterGetFlag(i, had); - } - } - ImGui::Spacing(); - if (ImGui::Button("Receive All##letters_all")) { - for (int i = 0; i < LETTER_COUNT; i++) setLetterGetFlag(i, true); - } - ImGui::SameLine(); - if (ImGui::Button("Clear All##letters_clear")) { - for (int i = 0; i < LETTER_COUNT; i++) setLetterGetFlag(i, false); - } - ImGuiEndGroupPanel(); - - ImGuiBeginGroupPanel("Fishing Log", { 360, 0 }); - { - dSv_fishing_info_c& fish = dComIfGs_getSaveData()->getPlayer().getFishingInfo(); - if (ImGui::BeginTable("FishTable", 3, + if (ImGui::TreeNode("Golden Bugs")) { + if (ImGui::BeginTable("GoldenBugTable", 5, ImGuiTableFlags_SizingFixedFit | ImGuiTableFlags_RowBg)) { ImGui::TableSetupColumn("Species"); - ImGui::TableSetupColumn("Caught"); - ImGui::TableSetupColumn("Biggest (cm)"); + ImGui::TableSetupColumn("Male"); + ImGui::TableSetupColumn("Female"); + ImGui::TableSetupColumn("M \xe2\x86\x92 Agitha"); // M → Agitha + ImGui::TableSetupColumn("F \xe2\x86\x92 Agitha"); // F → Agitha ImGui::TableHeadersRow(); - for (int i = 0; i < FISH_COUNT; i++) { - u8 idx = sFishSpecies[i].index; + for (int species = 0; species < BUG_SPECIES_COUNT; species++) { + int maleIdx = species * 2; + int femaleIdx = species * 2 + 1; + u8 maleItem = sBugItemIds[maleIdx]; + u8 femaleItem = sBugItemIds[femaleIdx]; + u16 maleFlag = sBugTurnInFlags[maleIdx]; + u16 femaleFlag = sBugTurnInFlags[femaleIdx]; + ImGui::TableNextRow(); ImGui::TableSetColumnIndex(0); - ImGui::TextUnformatted(sFishSpecies[i].name); + ImGui::TextUnformatted(sBugSpeciesNames[species]); ImGui::TableSetColumnIndex(1); - int count = dComIfGs_getFishNum(idx); - ImGui::SetNextItemWidth(100.0f); - if (ImGui::InputInt(fmt::format("##fish_c_{}", i).c_str(), &count, 1, 10)) { - if (count < 0) count = 0; - if (count > 999) count = 999; - fish.mFishCount[idx] = (u16)count; + bool maleOwned = dComIfGs_isItemFirstBit(maleItem) != 0; + if (ImGui::Checkbox(fmt::format("##bugM_own_{}", species).c_str(), &maleOwned)) { + setItemFirstBit(maleItem, maleOwned); } ImGui::TableSetColumnIndex(2); - int size = dComIfGs_getFishSize(idx); - ImGui::SetNextItemWidth(100.0f); - if (ImGui::InputInt(fmt::format("##fish_s_{}", i).c_str(), &size, 1, 10)) { - if (size < 0) size = 0; - if (size > 255) size = 255; - dComIfGs_setFishSize(idx, (u8)size); + bool femaleOwned = dComIfGs_isItemFirstBit(femaleItem) != 0; + if (ImGui::Checkbox(fmt::format("##bugF_own_{}", species).c_str(), &femaleOwned)) { + setItemFirstBit(femaleItem, femaleOwned); + } + + ImGui::TableSetColumnIndex(3); + bool maleGiven = dComIfGs_isEventBit(maleFlag) != 0; + if (ImGui::Checkbox(fmt::format("##bugM_giv_{}", species).c_str(), &maleGiven)) { + setEventBit(maleFlag, maleGiven); + } + + ImGui::TableSetColumnIndex(4); + bool femaleGiven = dComIfGs_isEventBit(femaleFlag) != 0; + if (ImGui::Checkbox(fmt::format("##bugF_giv_{}", species).c_str(), &femaleGiven)) { + setEventBit(femaleFlag, femaleGiven); } } + ImGui::EndTable(); } - } - ImGuiEndGroupPanel(); - ImGuiBeginGroupPanel("Heart Pieces", { 360, 0 }); - { - int maxLife = dComIfGs_getMaxLife(); - int hearts = maxLife / 5; - int pieces = maxLife % 5; - ImGui::Text("Max Life:"); - ImGui::SameLine(); - ImGui::SetNextItemWidth(160.0f); - if (ImGui::InputInt("##max_life", &maxLife, 1, 5)) { - if (maxLife < 15) maxLife = 15; - if (maxLife > 100) maxLife = 100; - dComIfGs_setMaxLife((u8)maxLife); - u16 maxHealth = (dComIfGs_getMaxLife() / 5) * 4; - if (dComIfGs_getLife() > maxHealth) { - dComIfGs_setLife(maxHealth); + ImGui::Spacing(); + if (ImGui::Button("Collect All##bugs_all")) { + for (int i = 0; i < BUG_SPECIES_COUNT * 2; i++) { + dComIfGs_onItemFirstBit(sBugItemIds[i]); } } ImGui::SameLine(); - ImGui::TextDisabled("(%d hearts + %d pieces)", hearts, pieces); - ImGui::Spacing(); - if (ImGui::Button("3 Hearts##hp_min")) { - dComIfGs_setMaxLife(15); - dComIfGs_setLife(12); + if (ImGui::Button("Clear All##bugs_clear")) { + for (int i = 0; i < BUG_SPECIES_COUNT * 2; i++) { + dComIfGs_offItemFirstBit(sBugItemIds[i]); + dComIfGs_offEventBit(sBugTurnInFlags[i]); + } } ImGui::SameLine(); - if (ImGui::Button("20 Hearts##hp_max")) { - dComIfGs_setMaxLife(100); - dComIfGs_setLife(80); + if (ImGui::Button("Give All to Agitha##bugs_giveall")) { + for (int i = 0; i < BUG_SPECIES_COUNT * 2; i++) { + dComIfGs_onEventBit(sBugTurnInFlags[i]); + } } + ImGui::TreePop(); + } + + if (ImGui::TreeNode("Hidden Skills")) { + for (int i = 0; i < HIDDEN_SKILL_COUNT; i++) { + bool learned = dComIfGs_isEventBit(sHiddenSkillFlags[i]) != 0; + if (ImGui::Checkbox( + fmt::format("{0}##skill_{1}", sHiddenSkillNames[i], i).c_str(), &learned)) { + setEventBit(sHiddenSkillFlags[i], learned); + } + } + ImGui::Spacing(); + if (ImGui::Button("Learn All##skills_all")) { + for (int i = 0; i < HIDDEN_SKILL_COUNT; i++) { + dComIfGs_onEventBit(sHiddenSkillFlags[i]); + } + } + ImGui::SameLine(); + if (ImGui::Button("Forget All##skills_clear")) { + for (int i = 0; i < HIDDEN_SKILL_COUNT; i++) { + dComIfGs_offEventBit(sHiddenSkillFlags[i]); + } + } + ImGui::TreePop(); + } + + if (ImGui::TreeNode("Collection Logs")) { + if (ImGui::TreeNode("Postman Letters")) { + for (int i = 0; i < LETTER_COUNT; i++) { + bool had = dComIfGs_isLetterGetFlag(i) != 0; + if (ImGui::Checkbox( + fmt::format("{0}##letter_{1}", sLetterSenders[i], i).c_str(), &had)) { + setLetterGetFlag(i, had); + } + } + ImGui::Spacing(); + if (ImGui::Button("Receive All##letters_all")) { + for (int i = 0; i < LETTER_COUNT; i++) setLetterGetFlag(i, true); + } + ImGui::SameLine(); + if (ImGui::Button("Clear All##letters_clear")) { + for (int i = 0; i < LETTER_COUNT; i++) setLetterGetFlag(i, false); + } + ImGui::TreePop(); + } + + if (ImGui::TreeNode("Fishing Log")) { + dSv_fishing_info_c& fish = dComIfGs_getSaveData()->getPlayer().getFishingInfo(); + if (ImGui::BeginTable("FishTable", 3, + ImGuiTableFlags_SizingFixedFit | ImGuiTableFlags_RowBg)) { + ImGui::TableSetupColumn("Species"); + ImGui::TableSetupColumn("Caught"); + ImGui::TableSetupColumn("Biggest (cm)"); + ImGui::TableHeadersRow(); + + for (int i = 0; i < FISH_COUNT; i++) { + u8 idx = sFishSpecies[i].index; + ImGui::TableNextRow(); + + ImGui::TableSetColumnIndex(0); + ImGui::TextUnformatted(sFishSpecies[i].name); + + ImGui::TableSetColumnIndex(1); + int count = dComIfGs_getFishNum(idx); + ImGui::SetNextItemWidth(100.0f); + if (ImGui::InputInt(fmt::format("##fish_c_{}", i).c_str(), &count, 1, 10)) { + if (count < 0) count = 0; + if (count > 999) count = 999; + fish.mFishCount[idx] = (u16)count; + } + + ImGui::TableSetColumnIndex(2); + int size = dComIfGs_getFishSize(idx); + ImGui::SetNextItemWidth(100.0f); + if (ImGui::InputInt(fmt::format("##fish_s_{}", i).c_str(), &size, 1, 10)) { + if (size < 0) size = 0; + if (size > 255) size = 255; + dComIfGs_setFishSize(idx, (u8)size); + } + } + ImGui::EndTable(); + } + ImGui::TreePop(); + } + ImGui::TreePop(); } - ImGuiEndGroupPanel(); } void drawFlagList(const char* id, BE(u32)& flags) { From bccc5167bc499f33d3396c28911097c926eaf8ae Mon Sep 17 00:00:00 2001 From: MelonSpeedruns Date: Sat, 11 Apr 2026 17:23:44 -0400 Subject: [PATCH 10/19] fix oddities --- src/dusk/imgui/ImGuiConsole.cpp | 2 +- src/dusk/settings.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/dusk/imgui/ImGuiConsole.cpp b/src/dusk/imgui/ImGuiConsole.cpp index 76a3d14260..d8716b57ab 100644 --- a/src/dusk/imgui/ImGuiConsole.cpp +++ b/src/dusk/imgui/ImGuiConsole.cpp @@ -244,7 +244,7 @@ namespace dusk { return; } - if (!m_isLaunchInitialized) { + if (dusk::IsGameLaunched && !m_isLaunchInitialized) { m_toasts.emplace_back("Press F1 to toggle menu"s, 5.f); m_isLaunchInitialized = true; } diff --git a/src/dusk/settings.cpp b/src/dusk/settings.cpp index 95589cc4d9..588e2817d3 100644 --- a/src/dusk/settings.cpp +++ b/src/dusk/settings.cpp @@ -64,7 +64,7 @@ UserSettings g_userSettings = { .isoPath {"backend.isoPath", ""}, .graphicsBackend {"backend.graphicsBackend", "auto"}, .skipPreLaunchUI {"backend.skipPreLaunchUI", false}, - .showPipelineCompilation{"backend.showPipelineCompilation", false}, + .showPipelineCompilation {"backend.showPipelineCompilation", false}, .wasPresetChosen{"backend.wasPresetChosen", false} } }; From 2e6a58b140c7104fed7f2d31c7ae350d0e3fdf80 Mon Sep 17 00:00:00 2001 From: MelonSpeedruns Date: Sat, 11 Apr 2026 17:38:42 -0400 Subject: [PATCH 11/19] yes i pushed into main like a mad man for a single space --- src/dusk/settings.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dusk/settings.cpp b/src/dusk/settings.cpp index 588e2817d3..889ebbaf28 100644 --- a/src/dusk/settings.cpp +++ b/src/dusk/settings.cpp @@ -65,7 +65,7 @@ UserSettings g_userSettings = { .graphicsBackend {"backend.graphicsBackend", "auto"}, .skipPreLaunchUI {"backend.skipPreLaunchUI", false}, .showPipelineCompilation {"backend.showPipelineCompilation", false}, - .wasPresetChosen{"backend.wasPresetChosen", false} + .wasPresetChosen {"backend.wasPresetChosen", false} } }; From 880c9f9e9e9976db54835b7a4068eb54e7bf94f0 Mon Sep 17 00:00:00 2001 From: Irastris Date: Sat, 11 Apr 2026 17:50:44 -0400 Subject: [PATCH 12/19] Frame interp: Fix d_kankyo lights --- src/m_Do/m_Do_graphic.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/m_Do/m_Do_graphic.cpp b/src/m_Do/m_Do_graphic.cpp index 22dffad197..c27598989a 100644 --- a/src/m_Do/m_Do_graphic.cpp +++ b/src/m_Do/m_Do_graphic.cpp @@ -29,6 +29,7 @@ #include "d/d_meter2_info.h" #include "d/d_s_play.h" #include "dusk/endian.h" +#include "dusk/frame_interpolation.h" #include "dusk/gx_helper.h" #include "dusk/logging.h" #include "f_ap/f_ap_game.h" @@ -39,7 +40,6 @@ #include "m_Do/m_Do_graphic.h" #include "m_Do/m_Do_machine.h" #include "m_Do/m_Do_main.h" -#include "dusk/frame_interpolation.h" #include "tracy/Tracy.hpp" #if PLATFORM_WII || PLATFORM_SHIELD @@ -1897,6 +1897,11 @@ int mDoGph_Painter() { j3dSys.setViewMtx(camera_p->view.viewMtx); #endif dKy_setLight(); +#if TARGET_PC + if (dusk::getSettings().game.enableFrameInterpolation) { + dKy_setLight_again(); + } +#endif GX_DEBUG_GROUP(dComIfGd_drawOpaListSky); GX_DEBUG_GROUP(dComIfGd_drawXluListSky); From b628a1beb6e8411bf938d1869d6fa7ff1cadac3a Mon Sep 17 00:00:00 2001 From: Irastris Date: Sat, 11 Apr 2026 18:19:09 -0400 Subject: [PATCH 13/19] Frame interp: Fix fmap scroll arrows --- include/d/d_menu_fmap2D.h | 3 +++ src/d/d_menu_fmap.cpp | 6 ++++++ src/d/d_menu_fmap2D.cpp | 6 ++++++ 3 files changed, 15 insertions(+) diff --git a/include/d/d_menu_fmap2D.h b/include/d/d_menu_fmap2D.h index c0344c7903..9b237f2771 100644 --- a/include/d/d_menu_fmap2D.h +++ b/include/d/d_menu_fmap2D.h @@ -152,6 +152,9 @@ public: void setRegionCursor(u8 i_value) { mRegionCursor = i_value; } void setMapDrawFlag(bool i_flag) { mMapDrawFlag = i_flag; } void resetDrug() { field_0x1238 = 0; } +#if TARGET_PC + void resetScrollArrowMask() { field_0x122d = 0; } +#endif void offArrowDrawFlag() { mArrowDrawFlag = false; } void onArrowDrawFlag() { mArrowDrawFlag = true; } diff --git a/src/d/d_menu_fmap.cpp b/src/d/d_menu_fmap.cpp index 652f3a06c5..149e03349d 100644 --- a/src/d/d_menu_fmap.cpp +++ b/src/d/d_menu_fmap.cpp @@ -1133,6 +1133,12 @@ void dMenu_Fmap_c::zoom_region_to_spot_proc() { void dMenu_Fmap_c::zoom_spot_to_region_init() { mZoomLevel = 10; field_0x1ec = 1.0f; +#if TARGET_PC + // Frame interp note: field_0x122d used to be set every draw, causing flickering. Do it here instead. + if (dusk::getSettings().game.enableFrameInterpolation) { + mpDraw2DBack->resetScrollArrowMask(); + } +#endif Z2GetAudioMgr()->seStart(Z2SE_SY_MAP_ZOOMOUT, NULL, 0, 0, 1.0f, 1.0f, -1.0f, -1.0f, 0); } diff --git a/src/d/d_menu_fmap2D.cpp b/src/d/d_menu_fmap2D.cpp index e016ed14dd..558ad05d6d 100644 --- a/src/d/d_menu_fmap2D.cpp +++ b/src/d/d_menu_fmap2D.cpp @@ -432,7 +432,13 @@ void dMenu_Fmap2DBack_c::draw() { if (field_0x122d) { mpMeterHaihai->drawHaihai(field_0x122d); +#if TARGET_PC + if (!dusk::getSettings().game.enableFrameInterpolation) { + field_0x122d = 0; + } +#else field_0x122d = 0; +#endif } if (g_fmapHIO.mRangeCheck && !g_fmapHIO.mRangeCheckDrawPriority) { From aafd50cd0906217b5fbe6339ca5219e74ee4fb3f Mon Sep 17 00:00:00 2001 From: Luke Street Date: Sat, 11 Apr 2026 17:50:52 -0600 Subject: [PATCH 14/19] Use Limiter class in waitForTick (#330) This uses the existing `Limiter` class (stolen from Metaforce) in `JFWDisplay::waitForTick`. The limiter also now uses `SDL_DelayPrecise` internally on non-Windows platforms. On Windows, the existing `NanoSleep` logic is untouched, as it appears to provide a more stable framerate for the folks testing it on Windows than `SDL_DelayPrecise` does. On Linux, however, `SDL_DelayPrecise` is plenty accurate. --- include/dusk/time.h | 8 +-- libs/JSystem/src/JFramework/JFWDisplay.cpp | 68 +++++++++++++--------- libs/JSystem/src/JUtility/JUTVideo.cpp | 2 + src/m_Do/m_Do_main.cpp | 1 - 4 files changed, 46 insertions(+), 33 deletions(-) diff --git a/include/dusk/time.h b/include/dusk/time.h index 94f819f76b..948a2fc171 100644 --- a/include/dusk/time.h +++ b/include/dusk/time.h @@ -15,12 +15,10 @@ #include #include #include +#else +#include "SDL3/SDL_timer.h" #endif -#include "dusk/logging.h" - -constexpr auto DUSK_FRAME_PERIOD = std::chrono::duration_cast(std::chrono::duration(1001.0 / 30000.0)); - class Limiter { using delta_clock = std::chrono::high_resolution_clock; using duration_t = std::chrono::nanoseconds; @@ -101,7 +99,7 @@ private: } while (current.QuadPart - start.QuadPart < ticksToWait); } #else - void NanoSleep(const duration_t duration) { std::this_thread::sleep_for(duration); } + void NanoSleep(const duration_t duration) { SDL_DelayPrecise(duration.count()); } #endif }; diff --git a/libs/JSystem/src/JFramework/JFWDisplay.cpp b/libs/JSystem/src/JFramework/JFWDisplay.cpp index 0ae8e95fa7..d3e7abf0a1 100644 --- a/libs/JSystem/src/JFramework/JFWDisplay.cpp +++ b/libs/JSystem/src/JFramework/JFWDisplay.cpp @@ -1,26 +1,30 @@ #include "JSystem/JSystem.h" // IWYU pragma: keep -#include -#include -#include -#include -#include -#include "SDL3/SDL_timer.h" -#include "JSystem/J2DGraph/J2DOrthoGraph.h" #include "JSystem/JFramework/JFWDisplay.h" +#include "JSystem/J2DGraph/J2DOrthoGraph.h" #include "JSystem/JKernel/JKRHeap.h" #include "JSystem/JUtility/JUTAssert.h" #include "JSystem/JUtility/JUTConsole.h" #include "JSystem/JUtility/JUTDbPrint.h" #include "JSystem/JUtility/JUTProcBar.h" -#include "aurora/aurora.h" +#include +#include +#include "global.h" +#include + +#ifdef TARGET_PC #include "dusk/dusk.h" #include "dusk/gx_helper.h" #include "dusk/logging.h" #include "dusk/settings.h" -#include "global.h" +#include "dusk/time.h" + +#include "SDL3/SDL_timer.h" #include "tracy/Tracy.hpp" +#include +#endif + void JFWDisplay::ctor_subroutine(bool enableAlpha) { mEnableAlpha = enableAlpha; mClamp = GX_CLAMP_TOP | GX_CLAMP_BOTTOM; @@ -377,6 +381,19 @@ void JFWDisplay::waitBlanking(int param_0) { } } +#if TARGET_PC +constexpr auto FRAME_PERIOD = std::chrono::duration_cast( + std::chrono::duration(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)); + dusk::frameUsagePct = + 100.0f * (1.0f - static_cast(sleepTime.count()) / static_cast(targetNs)); + limiter.Sleep(std::chrono::nanoseconds(targetNs)); +} +#endif + static void waitForTick(u32 p1, u16 p2) { #if TARGET_PC if (dusk::getSettings().game.enableFrameInterpolation) { @@ -385,44 +402,42 @@ static void waitForTick(u32 p1, u16 p2) { if (dusk::getTransientSettings().skipFrameRateLimit) { p1 = OS_TIMER_CLOCK / 120; } + ZoneScopedC(tracy::Color::DimGray); #endif - ZoneScopedC(tracy::Color::DimGray); - if (p1 != 0) - { + if (p1 != 0) { +#if TARGET_PC + static Limiter limiter; + waitPrecise(limiter, static_cast(OSTicksToMicroseconds(p1)) * 1000ULL); +#else static OSTime nextTick = OSGetTime(); OSTime time = OSGetTime(); - OSTime waitTime = (nextTick > time) ? (nextTick - time) : 0; while (time < nextTick) { JFWDisplay::getManager()->threadSleep((nextTick - time)); time = OSGetTime(); } - dusk::frameUsagePct = 100.0f * (1.0f - (float)waitTime / (float)p1); nextTick = time + p1; +#endif } else { - static u32 nextCount = VIGetRetraceCount(); u32 uVar1 = (p2 == 0) ? 1 : p2; +#if TARGET_PC + static Limiter limiter; + waitPrecise(limiter, static_cast((RETRACE_PERIOD * uVar1).count())); +#else + static u32 nextCount = VIGetRetraceCount(); OSMessage msg; do { if (!OSReceiveMessage(JUTVideo::getManager()->getMessageQueue(), &msg, - OS_MESSAGE_BLOCK)) - { + OS_MESSAGE_BLOCK)) { msg = 0; } } while (((intptr_t)msg - (intptr_t)nextCount) < 0); - dusk::frameUsagePct = 100.0f; nextCount = (intptr_t)msg + uVar1; +#endif } } JSUList JFWAlarm::sList(false); - -#if TARGET_PC -void JFWDisplay::threadSleep(s64 time) { - SDL_DelayNS(OSTicksToMicroseconds(time) * 1'000); -} -#else - static void JFWThreadAlarmHandler(OSAlarm* p_alarm, OSContext* p_ctx) { JFWAlarm* alarm = static_cast(p_alarm); alarm->removeLink(); @@ -440,7 +455,6 @@ void JFWDisplay::threadSleep(s64 time) { OSSuspendThread(alarm.getThread()); OSRestoreInterrupts(status); } -#endif static void dummy() { JUTXfb::getManager()->setDisplayingXfbIndex(0); @@ -480,7 +494,7 @@ void JFWDisplay::clearEfb(GXColor color) { void JFWDisplay::clearEfb(int param_0, int param_1, int param_2, int param_3, GXColor color) { STUB_RET(); - + u16 width; u16 height; Mtx44 mtx; diff --git a/libs/JSystem/src/JUtility/JUTVideo.cpp b/libs/JSystem/src/JUtility/JUTVideo.cpp index bb1f2cee3b..f718fcac23 100644 --- a/libs/JSystem/src/JUtility/JUTVideo.cpp +++ b/libs/JSystem/src/JUtility/JUTVideo.cpp @@ -182,7 +182,9 @@ void JUTVideo::postRetraceProc(u32 retrace_count) { sManager->mPostCallback(retrace_count); } +#ifndef TARGET_PC // Not read by JFWDisplay waitForTick OSSendMessage(&sManager->mMessageQueue, (OSMessage)(uintptr_t)VIGetRetraceCount(), OS_MESSAGE_NOBLOCK); +#endif } void JUTVideo::setRenderMode(GXRenderModeObj const* pObj) { diff --git a/src/m_Do/m_Do_main.cpp b/src/m_Do/m_Do_main.cpp index 1be3a06b44..4dd1a658e5 100644 --- a/src/m_Do/m_Do_main.cpp +++ b/src/m_Do/m_Do_main.cpp @@ -53,7 +53,6 @@ #include "dusk/main.h" #include "dusk/imgui/ImGuiConsole.hpp" #include "version.h" -#include "dusk/time.h" #include #include From eaf1b386a65063b63c61cbc1d021214d804bc465 Mon Sep 17 00:00:00 2001 From: MelonSpeedruns Date: Sat, 11 Apr 2026 19:51:37 -0400 Subject: [PATCH 15/19] Third Dusk Preset (#331) * fix aspect ratio not changing when applying the sd preset * Added a third preset, and renamed SD preset to Classic * show preset menu before setting the iso --------- Co-authored-by: MelonSpeedruns --- src/d/actor/d_a_title.cpp | 16 ------------ src/dusk/imgui/ImGuiConsole.cpp | 6 ++--- src/dusk/imgui/ImGuiEngine.cpp | 2 +- src/dusk/imgui/ImGuiFirstRunPreset.cpp | 34 ++++++++++++++++++++++---- 4 files changed, 32 insertions(+), 26 deletions(-) diff --git a/src/d/actor/d_a_title.cpp b/src/d/actor/d_a_title.cpp index 2bf5f0bccb..fc727a1248 100644 --- a/src/d/actor/d_a_title.cpp +++ b/src/d/actor/d_a_title.cpp @@ -18,10 +18,6 @@ #include "JSystem/J2DGraph/J2DTextBox.h" #include "m_Do/m_Do_graphic.h" -#if TARGET_PC -#include -#endif - class daTit_HIO_c : public JORReflexible { public: daTit_HIO_c(); @@ -156,12 +152,6 @@ int daTitle_c::createHeapCallBack(fopAc_ac_c* actor) { } int daTitle_c::Execute() { - #if TARGET_PC - if (!dusk::getSettings().backend.wasPresetChosen) { - return 0; - } - #endif - #if PLATFORM_WII || PLATFORM_SHIELD mDoGph_gInf_c::resetDimming(); #endif @@ -380,12 +370,6 @@ int daTitle_c::getDemoPrm() { } int daTitle_c::Draw() { - #if TARGET_PC - if (!dusk::getSettings().backend.wasPresetChosen) { - return 0; - } - #endif - J3DModelData* modelData = mpModel->getModelData(); cMtx_trans(mpModel->getBaseTRMtx(), IREG_F(7), IREG_F(8), IREG_F(9) + -430.0f); mpModel->getBaseScale()->x = -1.0f; diff --git a/src/dusk/imgui/ImGuiConsole.cpp b/src/dusk/imgui/ImGuiConsole.cpp index d8716b57ab..6e917761f6 100644 --- a/src/dusk/imgui/ImGuiConsole.cpp +++ b/src/dusk/imgui/ImGuiConsole.cpp @@ -237,10 +237,8 @@ namespace dusk { m_preLaunchWindow.draw(); } - if (!m_isLaunchInitialized && !getSettings().backend.wasPresetChosen) { - if (dusk::IsGameLaunched) { - m_firstRunPreset.draw(); - } + if (!getSettings().backend.wasPresetChosen) { + m_firstRunPreset.draw(); return; } diff --git a/src/dusk/imgui/ImGuiEngine.cpp b/src/dusk/imgui/ImGuiEngine.cpp index 03267df97a..eb7bf4c6bc 100644 --- a/src/dusk/imgui/ImGuiEngine.cpp +++ b/src/dusk/imgui/ImGuiEngine.cpp @@ -144,7 +144,7 @@ void ImGuiEngine_Initialize(float scale) { colors[ImGuiCol_NavHighlight] = ImVec4(0.26f, 0.59f, 0.98f, 1.00f); colors[ImGuiCol_NavWindowingHighlight] = ImVec4(1.00f, 1.00f, 1.00f, 0.70f); colors[ImGuiCol_NavWindowingDimBg] = ImVec4(0.80f, 0.80f, 0.80f, 0.20f); - colors[ImGuiCol_ModalWindowDimBg] = ImVec4(0.80f, 0.80f, 0.80f, 0.35f); + colors[ImGuiCol_ModalWindowDimBg] = ImVec4(0.00f, 0.00f, 0.00f, 0.60f); } Image GetImage(const std::string& path) { diff --git a/src/dusk/imgui/ImGuiFirstRunPreset.cpp b/src/dusk/imgui/ImGuiFirstRunPreset.cpp index 7fa5c11fa6..be4866fd6a 100644 --- a/src/dusk/imgui/ImGuiFirstRunPreset.cpp +++ b/src/dusk/imgui/ImGuiFirstRunPreset.cpp @@ -5,12 +5,14 @@ #include "ImGuiEngine.hpp" #include "dusk/settings.h" #include "dusk/config.hpp" +#include namespace dusk { -static void ApplyPresetStandard() { +static void ApplyPresetClassic() { auto& s = getSettings(); s.video.lockAspectRatio.setValue(true); + VILockAspectRatio(defaultAspectRatioW, defaultAspectRatioH); } static void ApplyPresetHD() { @@ -26,6 +28,16 @@ static void ApplyPresetHD() { s.game.invertCameraXAxis.setValue(true); } +static void ApplyPresetDusk() { + ApplyPresetHD(); + + auto& s = getSettings(); + s.game.enableQuickTransform.setValue(true); + s.game.instantSaves.setValue(true); + s.game.midnasLamentNonStop.setValue(true); + s.game.enableFrameInterpolation.setValue(true); +} + // ========================================================================= void ImGuiFirstRunPreset::draw() { @@ -55,18 +67,19 @@ void ImGuiFirstRunPreset::draw() { int chosen = -1; - if (ImGui::BeginTable("##presets", 3, ImGuiTableFlags_None)) { + if (ImGui::BeginTable("##presets", 5, ImGuiTableFlags_None)) { + ImGui::TableSetupColumn(nullptr, ImGuiTableColumnFlags_WidthStretch); + ImGui::TableSetupColumn(nullptr, ImGuiTableColumnFlags_WidthFixed, 16.0f * ImGuiScale()); ImGui::TableSetupColumn(nullptr, ImGuiTableColumnFlags_WidthStretch); ImGui::TableSetupColumn(nullptr, ImGuiTableColumnFlags_WidthFixed, 16.0f * ImGuiScale()); ImGui::TableSetupColumn(nullptr, ImGuiTableColumnFlags_WidthStretch); - ImGui::TableNextRow(); ImGui::PushFont(ImGuiEngine::fontLarge); ImGui::TableSetColumnIndex(0); - if (ImGui::Button("Standard##btn", ImVec2(ImGui::GetContentRegionAvail().x, 80.0f * ImGuiScale()))) { + if (ImGui::Button("Classic##btn", ImVec2(ImGui::GetContentRegionAvail().x, 80.0f * ImGuiScale()))) { chosen = 0; } @@ -75,6 +88,12 @@ void ImGuiFirstRunPreset::draw() { chosen = 1; } + ImGui::TableSetColumnIndex(4); + if (ImGui::Button("Dusk##btn", ImVec2(ImGui::GetContentRegionAvail().x, 80.0f * ImGuiScale()))) + { + chosen = 2; + } + ImGui::PopFont(); ImGui::TableNextRow(); @@ -87,12 +106,17 @@ void ImGuiFirstRunPreset::draw() { ImGui::Spacing(); ImGui::TextWrapped("Some enhancements enabled to match the HD version. A good starting point for most players!"); + ImGui::TableSetColumnIndex(4); + ImGui::Spacing(); + ImGui::TextWrapped("More enhancements enabled than the HD preset. Veteran players will appreciate the additional tweaks!"); + ImGui::EndTable(); } if (chosen >= 0) { - if (chosen == 0) ApplyPresetStandard(); + if (chosen == 0) ApplyPresetClassic(); if (chosen == 1) ApplyPresetHD(); + if (chosen == 2) ApplyPresetDusk(); getSettings().backend.wasPresetChosen.setValue(true); config::Save(); From 62df2c0460857a701c04d97343a79b84cb0f8279 Mon Sep 17 00:00:00 2001 From: MelonSpeedruns Date: Sat, 11 Apr 2026 19:52:08 -0400 Subject: [PATCH 16/19] Shadow Quality Enhancement (#311) * Shadow Quality Enhancement * added 1 space because we like consistency here * Addressed encounter comments * Renamed description of shadow resolution * added period at the end of shadow resolution description --------- Co-authored-by: MelonSpeedruns --- include/dusk/settings.h | 1 + src/d/d_drawlist.cpp | 18 +++++++++++++++++- src/dusk/imgui/ImGuiMenuEnhancements.cpp | 5 +++++ src/dusk/settings.cpp | 2 ++ 4 files changed, 25 insertions(+), 1 deletion(-) diff --git a/include/dusk/settings.h b/include/dusk/settings.h index cc867474da..d9d1ae154b 100644 --- a/include/dusk/settings.h +++ b/include/dusk/settings.h @@ -54,6 +54,7 @@ struct UserSettings { ConfigVar enableBloom; ConfigVar useWaterProjectionOffset; ConfigVar enableFrameInterpolation; + ConfigVar shadowResolutionMultiplier; // Audio ConfigVar noLowHpSound; diff --git a/src/d/d_drawlist.cpp b/src/d/d_drawlist.cpp index 1474b6f24d..f5dd0f23f5 100644 --- a/src/d/d_drawlist.cpp +++ b/src/d/d_drawlist.cpp @@ -1441,7 +1441,11 @@ void dDlst_shadowSimple_c::set(cXyz* param_0, f32 param_1, f32 param_2, cXyz* pa void dDlst_shadowControl_c::init() { #if TARGET_PC // Increase shadow map resolution - static u16 l_realImageSize[2] = {1024, 512}; + u16 l_realImageSize[2] = + { + 192 * dusk::getSettings().game.shadowResolutionMultiplier, + 64 * dusk::getSettings().game.shadowResolutionMultiplier + }; #else static u16 l_realImageSize[2] = {192, 64}; #endif @@ -1480,7 +1484,19 @@ void dDlst_shadowControl_c::reset() { #endif } +#if TARGET_PC +int lastShadowValue = 0; +#endif + void dDlst_shadowControl_c::imageDraw(Mtx param_0) { + #if TARGET_PC + if (lastShadowValue != dusk::getSettings().game.shadowResolutionMultiplier) { + reset(); + init(); + lastShadowValue = dusk::getSettings().game.shadowResolutionMultiplier; + } + #endif + static u8 l_matDL[] ATTRIBUTE_ALIGN(32) = { 0x10, 0x00, 0x00, 0x10, 0x0E, 0x00, 0x00, 0x04, 0x00, 0x10, 0x00, 0x00, 0x10, 0x10, 0x00, 0x00, 0x04, 0x00, 0x61, 0x28, 0x38, 0x00, 0x00, 0x61, 0xC0, 0x08, 0xFF, 0xF2, diff --git a/src/dusk/imgui/ImGuiMenuEnhancements.cpp b/src/dusk/imgui/ImGuiMenuEnhancements.cpp index 39cf09a9cd..b505d84345 100644 --- a/src/dusk/imgui/ImGuiMenuEnhancements.cpp +++ b/src/dusk/imgui/ImGuiMenuEnhancements.cpp @@ -85,6 +85,11 @@ namespace dusk { ImGui::SetTooltip("Uses inter-frame interpolation to enable higher frame rates.\nVisual artifacts, animation glitches, or instability may occur."); } + 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::EndMenu(); } diff --git a/src/dusk/settings.cpp b/src/dusk/settings.cpp index 889ebbaf28..4768abef93 100644 --- a/src/dusk/settings.cpp +++ b/src/dusk/settings.cpp @@ -42,6 +42,7 @@ UserSettings g_userSettings = { .enableBloom {"game.enableBloom", true}, .useWaterProjectionOffset {"game.useWaterProjectionOffset", false}, .enableFrameInterpolation = {"game.enableFrameInterpolation", false}, + .shadowResolutionMultiplier {"game.shadowResolutionMultiplier", 1}, // Audio .noLowHpSound {"game.noLowHpSound", false}, @@ -103,6 +104,7 @@ void registerSettings() { Register(g_userSettings.game.invertCameraXAxis); Register(g_userSettings.game.enableBloom); Register(g_userSettings.game.useWaterProjectionOffset); + Register(g_userSettings.game.shadowResolutionMultiplier); Register(g_userSettings.game.enableFastIronBoots); Register(g_userSettings.game.canTransformAnywhere); Register(g_userSettings.game.freeMagicArmor); From 0e2bd104b1adb62a4e66af7577d01894834850b3 Mon Sep 17 00:00:00 2001 From: Irastris Date: Sat, 11 Apr 2026 21:45:54 -0400 Subject: [PATCH 17/19] Frame interp: Fix Epona's reins --- include/d/actor/d_a_horse.h | 3 ++ include/d/d_drawlist.h | 4 ++ include/dusk/frame_interpolation.h | 5 ++ include/m_Do/m_Do_ext.h | 19 +++++++ src/d/actor/d_a_horse.cpp | 53 ++++++++++++++++++++ src/d/d_drawlist.cpp | 10 ++++ src/dusk/frame_interpolation.cpp | 11 +++++ src/m_Do/m_Do_ext.cpp | 79 ++++++++++++++++++++++++++++-- src/m_Do/m_Do_graphic.cpp | 13 +++++ 9 files changed, 192 insertions(+), 5 deletions(-) diff --git a/include/d/actor/d_a_horse.h b/include/d/actor/d_a_horse.h index 37e4bac70f..7e8ef8c343 100644 --- a/include/d/actor/d_a_horse.h +++ b/include/d/actor/d_a_horse.h @@ -196,6 +196,9 @@ public: void copyReinPos(); void setReinPosHandSubstance(int); void setReinPosNormalSubstance(); +#if TARGET_PC + void lerpControlPoints(f32 alpha); +#endif void bgCheck(); bool checkSpecialWallHitSubstance(cXyz const&) const; void setServiceWaitTimer(); diff --git a/include/d/d_drawlist.h b/include/d/d_drawlist.h index 80dc9035df..9eecac2d61 100644 --- a/include/d/d_drawlist.h +++ b/include/d/d_drawlist.h @@ -435,6 +435,10 @@ public: m3DLineMatSortPacket[param_1->getMaterialID()].setMatDark(param_1); } +#if TARGET_PC + void refresh3DlineMats(const cXyz& eye); +#endif + void peekZdata() { mPeekZ.peekData(); } void entryZSortListZxlu(J3DPacket* i_packet, cXyz& param_1) { entryZSortXluDrawList(mDrawBuffers[DB_LIST_Z_XLU], i_packet, param_1); diff --git a/include/dusk/frame_interpolation.h b/include/dusk/frame_interpolation.h index 3dfa00d53b..98495d18db 100644 --- a/include/dusk/frame_interpolation.h +++ b/include/dusk/frame_interpolation.h @@ -6,6 +6,8 @@ #include #include +struct cXyz; + #ifdef __cplusplus namespace dusk { namespace frame_interp { @@ -15,6 +17,7 @@ void ensure_initialized(); void begin_record(); void end_record(); void interpolate(float step); +float get_interpolation_step(); void notify_sim_tick_complete(); uint32_t begin_presentation_ui_pass(); uint32_t get_presentation_ui_advance_ticks(); @@ -27,6 +30,8 @@ void record_final_mtx_raw(const Mtx* dest, const Mtx src); bool lookup_replacement(const void* source, Mtx out); bool lookup_concat_replacement(const void* lhs, const void* rhs, Mtx out); +void camera_eye_from_view_mtx(MtxP view_mtx, cXyz* o_eye); + } // namespace frame_interp } // namespace dusk #endif diff --git a/include/m_Do/m_Do_ext.h b/include/m_Do/m_Do_ext.h index e6973dc782..77d046f0de 100644 --- a/include/m_Do/m_Do_ext.h +++ b/include/m_Do/m_Do_ext.h @@ -541,6 +541,9 @@ public: virtual int getMaterialID() = 0; virtual void setMaterial() = 0; virtual void draw() = 0; +#if TARGET_PC + virtual void refreshGeometryForPresentationEye(const cXyz& eye) {} +#endif /* 0x4 */ mDoExt_3DlineMat_c* field_0x4; }; @@ -582,11 +585,19 @@ class dKy_tevstr_c; class mDoExt_3DlineMat1_c : public mDoExt_3DlineMat_c { public: int init(u16, u16, ResTIMG*, int); +#if TARGET_PC + void update(int, GXColor&, dKy_tevstr_c*, const cXyz* presentationEye = nullptr); + void update(int, f32, GXColor&, u16, dKy_tevstr_c*, const cXyz* presentationEye = nullptr); +#else void update(int, GXColor&, dKy_tevstr_c*); void update(int, f32, GXColor&, u16, dKy_tevstr_c*); +#endif int getMaterialID() { return 1; } void setMaterial(); void draw(); +#if TARGET_PC + void refreshGeometryForPresentationEye(const cXyz& eye) override; +#endif cXyz* getPos(int i_idx) { return mpLines[i_idx].field_0x0; } f32* getSize(int i_idx) { return mpLines[i_idx].field_0x4; } @@ -600,6 +611,11 @@ private: /* 0x34 */ u16 field_0x34; /* 0x36 */ u8 mIsDrawn; /* 0x38 */ mDoExt_3Dline_c* mpLines; +#if TARGET_PC + u8 mInterpLineKind; + f32 mInterpLineF; + u16 mInterpLineU16; +#endif }; class mDoExt_3DlineMat2_c : public mDoExt_3DlineMat1_c { @@ -616,6 +632,9 @@ public: void setMatDark(mDoExt_3DlineMat_c* i_mat) { setMat(i_mat); } void setMat(mDoExt_3DlineMat_c*); +#if TARGET_PC + mDoExt_3DlineMat_c* getFirstMat() const { return mp3DlineMat; } +#endif virtual void draw(); virtual ~mDoExt_3DlineMatSortPacket() {} diff --git a/src/d/actor/d_a_horse.cpp b/src/d/actor/d_a_horse.cpp index 976e398616..68112eba47 100644 --- a/src/d/actor/d_a_horse.cpp +++ b/src/d/actor/d_a_horse.cpp @@ -20,6 +20,21 @@ #include #include +#if TARGET_PC +#include "dusk/dusk.h" + +namespace { +// FRAME INTERP NOTE: Sim tick control point snapshots for interpolation +constexpr int kHorseReinSimMax = 75; +cXyz s_horseReinSimPrev[kHorseReinSimMax]; +cXyz s_horseReinSimCurr[kHorseReinSimMax]; +int s_horseReinSimNumPrev; +int s_horseReinSimNumCurr; +bool s_horseReinSimPrevValid; +bool s_horseReinSimCurrValid; +} // namespace +#endif + #define ANM_HS_BACK_WALK 6 #define ANM_HS_WALK_START 7 #define ANM_HS_EXCITEMENT 8 @@ -3016,6 +3031,20 @@ void daHorse_c::copyReinPos() { for (i = rein->field_0x8[0] - 1; i >= 0; i--, pos_p++) { *pos_p = rein->field_0x0[0][i]; } +#if TARGET_PC + if (field_0x1204 > 0) { + if (s_horseReinSimCurrValid && s_horseReinSimNumCurr > 0) { + memcpy(s_horseReinSimPrev, s_horseReinSimCurr, s_horseReinSimNumCurr * sizeof(cXyz)); + s_horseReinSimNumPrev = s_horseReinSimNumCurr; + s_horseReinSimPrevValid = true; + } + memcpy(s_horseReinSimCurr, m_reinLine.getPos(0), field_0x1204 * sizeof(cXyz)); + s_horseReinSimNumCurr = field_0x1204; + s_horseReinSimCurrValid = true; + } else { + s_horseReinSimCurrValid = false; + } +#endif } void daHorse_c::setReinPosHandSubstance(int param_0) { @@ -3127,6 +3156,30 @@ void daHorse_c::setReinPosNormalSubstance() { copyReinPos(); } +#if TARGET_PC +void daHorse_c::lerpControlPoints(f32 alpha) { + // FRAME INTERP NOTE: Currently only lerping points for Epona's reins. Need a more global solution. + if (!dusk::getSettings().game.enableFrameInterpolation || !s_horseReinSimPrevValid || !s_horseReinSimCurrValid) { + return; + } + const int nCurr = s_horseReinSimNumCurr; + const int nPrev = s_horseReinSimNumPrev; + if (nCurr <= 0) { + return; + } + int n = nPrev < nCurr ? nPrev : nCurr; + if (n <= 0 || n > kHorseReinSimMax) { + return; + } + cXyz* dst = m_reinLine.getPos(0); + for (int i = 0; i < n; i++) { + const cXyz& p0 = s_horseReinSimPrev[i]; + const cXyz& p1 = s_horseReinSimCurr[i]; + dst[i] = p0 + (p1 - p0) * alpha; + } +} +#endif + void daHorse_c::bgCheck() { if (m_procID != PROC_LARGE_DAMAGE_e) { static cXyz localCenterPos(0.0f, 100.0f, 0.0f); diff --git a/src/d/d_drawlist.cpp b/src/d/d_drawlist.cpp index f5dd0f23f5..9f2255b884 100644 --- a/src/d/d_drawlist.cpp +++ b/src/d/d_drawlist.cpp @@ -2017,3 +2017,13 @@ void dDlst_list_c::calcWipe() { dComIfGd_set2DXlu(&mWipeDlst); } } + +#if TARGET_PC +void dDlst_list_c::refresh3DlineMats(const cXyz& eye) { + for (int i = 0; i < 3; i++) { + for (mDoExt_3DlineMat_c* mat = m3DLineMatSortPacket[i].getFirstMat(); mat != NULL; mat = mat->field_0x4) { + mat->refreshGeometryForPresentationEye(eye); + } + } +} +#endif diff --git a/src/dusk/frame_interpolation.cpp b/src/dusk/frame_interpolation.cpp index a3777dea7e..0c21b2b5af 100644 --- a/src/dusk/frame_interpolation.cpp +++ b/src/dusk/frame_interpolation.cpp @@ -300,6 +300,10 @@ void interpolate(float step) { interpolate_branch(g_previous_recording.root, g_current_recording.root, g_step); } +float get_interpolation_step() { + return g_step; +} + void notify_sim_tick_complete() { ensure_initialized(); g_pending_presentation_ui_ticks++; @@ -394,5 +398,12 @@ bool lookup_concat_replacement(const void* lhs, const void* rhs, Mtx out) { return true; } +// TODO: Is there already a built-in function for this? +void camera_eye_from_view_mtx(MtxP view_mtx, cXyz* o_eye) { + o_eye->x = -(view_mtx[0][0] * view_mtx[0][3] + view_mtx[1][0] * view_mtx[1][3] + view_mtx[2][0] * view_mtx[2][3]); + o_eye->y = -(view_mtx[0][1] * view_mtx[0][3] + view_mtx[1][1] * view_mtx[1][3] + view_mtx[2][1] * view_mtx[2][3]); + o_eye->z = -(view_mtx[0][2] * view_mtx[0][3] + view_mtx[1][2] * view_mtx[1][3] + view_mtx[2][2] * view_mtx[2][3]); +} + } // namespace frame_interp } // namespace dusk diff --git a/src/m_Do/m_Do_ext.cpp b/src/m_Do/m_Do_ext.cpp index 0593815b1d..577ada0837 100644 --- a/src/m_Do/m_Do_ext.cpp +++ b/src/m_Do/m_Do_ext.cpp @@ -2398,7 +2398,12 @@ void mDoExt_3DlineMat0_c::draw() { var_r28++; } - field_0x16 ^= (u8)1; +#if TARGET_PC + if (!dusk::getSettings().game.enableFrameInterpolation) +#endif + { + field_0x16 ^= (u8)1; + } } void mDoExt_3DlineMat0_c::update(int param_0, f32 param_1, GXColor& param_2, u16 param_3, @@ -2648,6 +2653,9 @@ int mDoExt_3DlineMat1_c::init(u16 param_0, u16 param_1, ResTIMG* param_2, int pa field_0x4 = 0; mIsDrawn = 0; +#if TARGET_PC + mInterpLineKind = 0; +#endif GXInitTexObj(&mTextureObject, (void*)((intptr_t)param_2 + param_2->imageOffset), param_2->width, param_2->height, (GXTexFmt)param_2->format, (GXTexWrapMode)param_2->wrapS, @@ -2720,11 +2728,19 @@ void mDoExt_3DlineMat1_c::draw() { lines++; } GXSetTexCoordScaleManually(GX_TEXCOORD0, 0, 0, 0); - mIsDrawn ^= (u8)1; +#if TARGET_PC + if (!dusk::getSettings().game.enableFrameInterpolation) +#endif + { + mIsDrawn ^= (u8)1; + } } -void mDoExt_3DlineMat1_c::update(int param_0, f32 param_1, GXColor& param_2, u16 param_3, - dKy_tevstr_c* param_4) { +#if TARGET_PC +void mDoExt_3DlineMat1_c::update(int param_0, f32 param_1, GXColor& param_2, u16 param_3, dKy_tevstr_c* param_4, const cXyz* presentationEye) { +#else +void mDoExt_3DlineMat1_c::update(int param_0, f32 param_1, GXColor& param_2, u16 param_3, dKy_tevstr_c* param_4) { +#endif mColor = param_2; this->mpTevStr = param_4; if (param_0 < 0) { @@ -2734,6 +2750,13 @@ void mDoExt_3DlineMat1_c::update(int param_0, f32 param_1, GXColor& param_2, u16 } else { field_0x34 = param_0; } + +#if TARGET_PC + mInterpLineKind = 1; + mInterpLineF = param_1; + mInterpLineU16 = param_3; +#endif + view_class* sp_3c = dComIfGd_getView(); mDoExt_3Dline_c* sp_38 = mpLines; f32 local_f27 = param_3 != 0 ? param_1 / param_3 : 0.0f; @@ -2787,7 +2810,12 @@ void mDoExt_3DlineMat1_c::update(int param_0, f32 param_1, GXColor& param_2, u16 local_f31 += local_f30 * 0.02f * (8.0f / param_1); } +#if TARGET_PC + const cXyz& lineEye = (presentationEye != nullptr && dusk::getSettings().game.enableFrameInterpolation) ? *presentationEye : sp_3c->lookat.eye; + sp_13c = *local_r27 - lineEye; +#else sp_13c = *local_r27 - sp_3c->lookat.eye; +#endif sp_130 = sp_130.outprod(sp_13c); sp_130.normalizeZP(); @@ -2822,7 +2850,11 @@ void mDoExt_3DlineMat1_c::update(int param_0, f32 param_1, GXColor& param_2, u16 local_f31 += local_f30 * 0.02f * (8.0f / param_1); } +#if TARGET_PC + sp_13c = local_r27[0] - lineEye; +#else sp_13c = local_r27[0] - sp_3c->lookat.eye; +#endif sp_130 = sp_130.outprod(sp_13c); sp_130.normalizeZP(); @@ -2894,7 +2926,11 @@ void mDoExt_3DlineMat2_c::setMaterial() { GXLoadNrmMtxImm(cMtx_getIdentity(), 0); } -void mDoExt_3DlineMat1_c::update(int param_0, GXColor& param_2, dKy_tevstr_c* param_4) { +#if TARGET_PC +void mDoExt_3DlineMat1_c::update(int param_0, GXColor& param_2, dKy_tevstr_c* param_4, const cXyz* presentationEye) { +#else +void mDoExt_3DlineMat1_c::update(int param_0, GXColor& param_2, dKy_tevstr_c* param_4 +#endif mColor = param_2; this->mpTevStr = param_4; if (param_0 < 0) { @@ -2904,6 +2940,11 @@ void mDoExt_3DlineMat1_c::update(int param_0, GXColor& param_2, dKy_tevstr_c* pa } else { field_0x34 = param_0; } + +#if TARGET_PC + mInterpLineKind = 2; +#endif + view_class* stack_3c = dComIfGd_getView(); mDoExt_3Dline_c* sp_38 = mpLines; f32 local_f30; @@ -2929,6 +2970,12 @@ void mDoExt_3DlineMat1_c::update(int param_0, GXColor& param_2, dKy_tevstr_c* pa for (s32 sp_14 = 0; sp_14 < mNumLines; sp_14++) { local_r27 = sp_38[0].field_0x0; size_p = sp_38->field_0x4; +#if TARGET_PC + if (presentationEye != nullptr && dusk::getSettings().game.enableFrameInterpolation && size_p == NULL) { + sp_38 += 1; + continue; + } +#endif JUT_ASSERT(5875, size_p != NULL); sp_24 = sp_38->field_0x8[mIsDrawn]; sp_28 = sp_24; @@ -2942,7 +2989,12 @@ void mDoExt_3DlineMat1_c::update(int param_0, GXColor& param_2, dKy_tevstr_c* pa sp_130 = local_r27[1] - local_r27[0]; local_f30 = sp_130.abs(); local_f31 += local_f30 * 0.1f; +#if TARGET_PC + const cXyz& lineEye = (presentationEye != nullptr && dusk::getSettings().game.enableFrameInterpolation) ? *presentationEye : stack_3c->lookat.eye; + sp_13c = local_r27[0] - lineEye; +#else sp_13c = local_r27[0] - stack_3c->lookat.eye; +#endif sp_130 = sp_130.outprod(sp_13c); sp_130.normalizeZP(); local_r30->x = sp_130.x * 64.0f; @@ -2964,7 +3016,11 @@ void mDoExt_3DlineMat1_c::update(int param_0, GXColor& param_2, dKy_tevstr_c* pa sp_130 = local_r27[1] - local_r27[0]; local_f30 = sp_130.abs(); local_f31 += local_f30 * 0.1f; +#if TARGET_PC + sp_13c = local_r27[0] - lineEye; +#else sp_13c = local_r27[0] - stack_3c->lookat.eye; +#endif sp_130 = sp_130.outprod(sp_13c); sp_130.normalizeZP(); local_r30 += 2; @@ -3008,6 +3064,19 @@ void mDoExt_3DlineMat1_c::update(int param_0, GXColor& param_2, dKy_tevstr_c* pa } } +#if TARGET_PC +void mDoExt_3DlineMat1_c::refreshGeometryForPresentationEye(const cXyz& eye) { + if (!dusk::getSettings().game.enableFrameInterpolation) { + return; + } + if (mInterpLineKind == 1) { + update(field_0x34, mInterpLineF, mColor, mInterpLineU16, mpTevStr, &eye); + } else if (mInterpLineKind == 2) { + update(field_0x34, mColor, mpTevStr, &eye); + } +} +#endif + void mDoExt_3DlineMatSortPacket::setMat(mDoExt_3DlineMat_c* i_3DlineMat) { if (mp3DlineMat == NULL) { dComIfGd_getListPacket()->entryImm(this, 0); diff --git a/src/m_Do/m_Do_graphic.cpp b/src/m_Do/m_Do_graphic.cpp index c27598989a..ff9e2042a0 100644 --- a/src/m_Do/m_Do_graphic.cpp +++ b/src/m_Do/m_Do_graphic.cpp @@ -51,6 +51,7 @@ #endif #if TARGET_PC +#include "d/actor/d_a_horse.h" #include "dusk/imgui/ImGuiConsole.hpp" #include "dusk/dusk.h" #endif @@ -1952,6 +1953,18 @@ int mDoGph_Painter() { GX_DEBUG_GROUP(dComIfGd_drawOpaListDark); } +#if TARGET_PC + if (dusk::getSettings().game.enableFrameInterpolation) { + cXyz pres_eye; + dusk::frame_interp::camera_eye_from_view_mtx(j3dSys.getViewMtx(), &pres_eye); + // FRAME INTERP NOTE: Currently only recalculating points for Epona's reins. Need a more global solution. + if (daHorse_c* horse = dComIfGp_getHorseActor()) { + horse->lerpControlPoints(dusk::frame_interp::get_interpolation_step()); + } + g_dComIfG_gameInfo.drawlist.refresh3DlineMats(pres_eye); + } +#endif + GX_DEBUG_GROUP(dComIfGd_drawOpaListPacket); #if DEBUG From 3c281cc9886f47129869f0aa91fd4cf12b9fb58a Mon Sep 17 00:00:00 2001 From: Luke Street Date: Sat, 11 Apr 2026 20:13:01 -0600 Subject: [PATCH 18/19] BE swap J3DMaterialFactory::newIndTexMtx/newFog --- .../include/JSystem/J3DGraphBase/J3DStruct.h | 3 --- .../src/J3DGraphLoader/J3DMaterialFactory.cpp | 19 +++++++++++++++++++ 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/libs/JSystem/include/JSystem/J3DGraphBase/J3DStruct.h b/libs/JSystem/include/JSystem/J3DGraphBase/J3DStruct.h index d970e14048..b565ad78f8 100644 --- a/libs/JSystem/include/JSystem/J3DGraphBase/J3DStruct.h +++ b/libs/JSystem/include/JSystem/J3DGraphBase/J3DStruct.h @@ -113,9 +113,6 @@ struct J3DFogInfo { bool operator==(J3DFogInfo&) const; J3DFogInfo& operator=(const J3DFogInfo&); - // TODO: Fog data should be converted from big endian (probably?) - // Not sure TP uses it. - /* 0x00 */ u8 mType; /* 0x01 */ u8 mAdjEnable; /* 0x02 */ u16 mCenter; diff --git a/libs/JSystem/src/J3DGraphLoader/J3DMaterialFactory.cpp b/libs/JSystem/src/J3DGraphLoader/J3DMaterialFactory.cpp index 4383632da7..7aa4d31cd4 100644 --- a/libs/JSystem/src/J3DGraphLoader/J3DMaterialFactory.cpp +++ b/libs/JSystem/src/J3DGraphLoader/J3DMaterialFactory.cpp @@ -9,6 +9,7 @@ #include "JSystem/JMath/JMath.h" #include "JSystem/JSupport/JSupport.h" #include "JSystem/JUtility/JUTAssert.h" +#include "dusk/logging.h" J3DMaterialFactory::J3DMaterialFactory(J3DMaterialBlock const& i_block) { mMaterialNum = i_block.mMaterialNum; @@ -656,7 +657,13 @@ J3DIndTexOrder J3DMaterialFactory::newIndTexOrder(int i_idx, int i_no) const { J3DIndTexMtx J3DMaterialFactory::newIndTexMtx(int i_idx, int i_no) const { J3DIndTexMtx dflt; if (mpIndInitData[i_idx].mEnabled == true) { +#if TARGET_LITTLE_ENDIAN + J3DIndTexMtxInfo indTexMtxInfo = mpIndInitData[i_idx].mIndTexMtxInfo[i_no]; + be_swap(indTexMtxInfo.field_0x0); + return indTexMtxInfo; +#else return J3DIndTexMtx(mpIndInitData[i_idx].mIndTexMtxInfo[i_no]); +#endif } else { return dflt; } @@ -684,7 +691,19 @@ J3DFog J3DMaterialFactory::newFog(int i_idx) const { J3DFog fog; J3DMaterialInitData* mtl_init_data = &mpMaterialInitData[mpMaterialID[i_idx]]; if (mtl_init_data->mFogIdx != 0xffff) { +#if TARGET_LITTLE_ENDIAN + J3DFogInfo fogInfo = mpFogInfo[mtl_init_data->mFogIdx]; + be_swap(fogInfo.mCenter); + be_swap(fogInfo.mStartZ); + be_swap(fogInfo.mEndZ); + be_swap(fogInfo.mNearZ); + be_swap(fogInfo.mFarZ); + for (int i = 0; i < 10; i++) + be_swap(fogInfo.mFogAdjTable.r[i]); + fog.setFogInfo(fogInfo); +#else fog.setFogInfo(mpFogInfo[mtl_init_data->mFogIdx]); +#endif } return fog; } From 85df64618d2c4940d527c3919685111a072c3b93 Mon Sep 17 00:00:00 2001 From: Luke Street Date: Sat, 11 Apr 2026 20:13:32 -0600 Subject: [PATCH 19/19] Add "Enable Water Refraction" toggle & cleanup --- include/d/d_com_inf_game.h | 13 ++++++++++--- include/dusk/settings.h | 2 +- src/d/actor/d_a_obj_groundwater.cpp | 8 -------- src/d/actor/d_a_obj_lv3Water.cpp | 7 ------- src/d/actor/d_a_obj_lv3Water2.cpp | 7 ------- src/d/actor/d_a_obj_lv3WaterB.cpp | 7 ------- src/d/actor/d_a_obj_rstair.cpp | 7 ------- src/d/actor/d_a_obj_tp.cpp | 7 ------- src/d/d_kankyo.cpp | 4 ---- src/dusk/imgui/ImGuiMenuGame.cpp | 6 +----- src/dusk/settings.cpp | 4 ++-- 11 files changed, 14 insertions(+), 58 deletions(-) diff --git a/include/d/d_com_inf_game.h b/include/d/d_com_inf_game.h index d64226c0db..d22d08cd10 100644 --- a/include/d/d_com_inf_game.h +++ b/include/d/d_com_inf_game.h @@ -4834,8 +4834,8 @@ inline void dComIfGd_drawXluListDark() { inline void dComIfGd_drawXluListInvisible() { ZoneScoped; #ifdef TARGET_PC - // FIXME: Water rendering hack for frame interpolation - if (!dusk::getSettings().game.enableFrameInterpolation) { + if (dusk::getSettings().game.enableWaterRefraction && + !dusk::getSettings().game.enableFrameInterpolation) { #endif g_dComIfG_gameInfo.drawlist.drawXluListInvisible(); #ifdef TARGET_PC @@ -4845,7 +4845,14 @@ inline void dComIfGd_drawXluListInvisible() { inline void dComIfGd_drawOpaListInvisible() { ZoneScoped; - g_dComIfG_gameInfo.drawlist.drawOpaListInvisible(); +#ifdef TARGET_PC + if (dusk::getSettings().game.enableWaterRefraction && + !dusk::getSettings().game.enableFrameInterpolation) { +#endif + g_dComIfG_gameInfo.drawlist.drawOpaListInvisible(); +#ifdef TARGET_PC + } +#endif } inline void dComIfGd_drawXluListZxlu() { diff --git a/include/dusk/settings.h b/include/dusk/settings.h index d9d1ae154b..8418602251 100644 --- a/include/dusk/settings.h +++ b/include/dusk/settings.h @@ -52,7 +52,7 @@ struct UserSettings { // Graphics ConfigVar enableBloom; - ConfigVar useWaterProjectionOffset; + ConfigVar enableWaterRefraction; ConfigVar enableFrameInterpolation; ConfigVar shadowResolutionMultiplier; diff --git a/src/d/actor/d_a_obj_groundwater.cpp b/src/d/actor/d_a_obj_groundwater.cpp index ab6b7af922..2d3d05a36c 100644 --- a/src/d/actor/d_a_obj_groundwater.cpp +++ b/src/d/actor/d_a_obj_groundwater.cpp @@ -300,19 +300,11 @@ int daGrdWater_c::Draw() { J3DTexMtxInfo* mtxInfo = &material->getTexGenBlock()->getTexMtx(0)->getTexMtxInfo(); if (mtxInfo != NULL) { Mtx afStack_50; - - #if TARGET_PC - C_MTXLightPerspective(afStack_50, dComIfGd_getView()->fovy, dComIfGd_getView()->aspect, - 1.0f, 1.0f, dusk::getSettings().game.useWaterProjectionOffset ? -0.01f : 0.0f, 0.0f); - #else C_MTXLightPerspective(afStack_50, dComIfGd_getView()->fovy, dComIfGd_getView()->aspect, 1.0f, 1.0f, -0.01f, 0.0f); - #endif - #if WIDESCREEN_SUPPORT mDoGph_gInf_c::setWideZoomLightProjection(afStack_50); #endif - mtxInfo->setEffectMtx(afStack_50); modelData2->simpleCalcMaterial(0, (MtxP)j3dDefaultMtx); } diff --git a/src/d/actor/d_a_obj_lv3Water.cpp b/src/d/actor/d_a_obj_lv3Water.cpp index 934cb3140d..d0b5490c8d 100644 --- a/src/d/actor/d_a_obj_lv3Water.cpp +++ b/src/d/actor/d_a_obj_lv3Water.cpp @@ -371,15 +371,8 @@ int daLv3Water_c::Draw() { texMtxInfo = &material->getTexGenBlock()->getTexMtx(0)->getTexMtxInfo(); if (texMtxInfo != NULL) { Mtx lightProjMtx; - - #if TARGET_PC - C_MTXLightPerspective(lightProjMtx, dComIfGd_getView()->fovy, - dComIfGd_getView()->aspect, 1.0f, 1.0f, dusk::getSettings().game.useWaterProjectionOffset ? -0.01f : 0.0f, 0.0f); - #else C_MTXLightPerspective(lightProjMtx, dComIfGd_getView()->fovy, dComIfGd_getView()->aspect, 1.0f, 1.0f, -0.01f, 0.0f); - #endif - #if WIDESCREEN_SUPPORT mDoGph_gInf_c::setWideZoomLightProjection(lightProjMtx); #endif diff --git a/src/d/actor/d_a_obj_lv3Water2.cpp b/src/d/actor/d_a_obj_lv3Water2.cpp index 55fc94d605..32995e646b 100644 --- a/src/d/actor/d_a_obj_lv3Water2.cpp +++ b/src/d/actor/d_a_obj_lv3Water2.cpp @@ -197,14 +197,7 @@ int daLv3Water2_c::Draw() { texMtxInfo = &btkMaterial->getTexGenBlock()->getTexMtx(0)->getTexMtxInfo(); if(texMtxInfo) { Mtx lightProjMtx; - - #if TARGET_PC - C_MTXLightPerspective(lightProjMtx, dComIfGd_getView()->fovy, - dComIfGd_getView()->aspect, 1.0f, 1.0f, dusk::getSettings().game.useWaterProjectionOffset ? -0.01f : 0.0f, 0); - #else C_MTXLightPerspective(lightProjMtx, dComIfGd_getView()->fovy, dComIfGd_getView()->aspect, 1.0f, 1.0f, -0.01f, 0); - #endif - #if WIDESCREEN_SUPPORT mDoGph_gInf_c::setWideZoomLightProjection(lightProjMtx); #endif diff --git a/src/d/actor/d_a_obj_lv3WaterB.cpp b/src/d/actor/d_a_obj_lv3WaterB.cpp index 33285d975f..f4ad49685d 100644 --- a/src/d/actor/d_a_obj_lv3WaterB.cpp +++ b/src/d/actor/d_a_obj_lv3WaterB.cpp @@ -27,15 +27,8 @@ static int daObj_Lv3waterB_Draw(obj_lv3WaterB_class* i_this) { J3DTexMtxInfo* tex_mtx_info = &material_p->getTexGenBlock()->getTexMtx(0)->getTexMtxInfo(); if (tex_mtx_info != NULL) { Mtx m; - - #if TARGET_PC - C_MTXLightPerspective(m, dComIfGd_getView()->fovy, dComIfGd_getView()->aspect, 1.0f, 1.0f, - dusk::getSettings().game.useWaterProjectionOffset ? -0.015f : 0.0f, 0.0f); - #else C_MTXLightPerspective(m, dComIfGd_getView()->fovy, dComIfGd_getView()->aspect, 1.0f, 1.0f, -0.015f, 0.0f); - #endif - #if WIDESCREEN_SUPPORT mDoGph_gInf_c::setWideZoomLightProjection(m); #endif diff --git a/src/d/actor/d_a_obj_rstair.cpp b/src/d/actor/d_a_obj_rstair.cpp index 7e44cc3700..609a0cf7b8 100644 --- a/src/d/actor/d_a_obj_rstair.cpp +++ b/src/d/actor/d_a_obj_rstair.cpp @@ -313,15 +313,8 @@ int daObjRotStair_c::Draw() { J3DTexMtxInfo* texMtxInfo = &material->getTexGenBlock()->getTexMtx(0)->getTexMtxInfo(); if (texMtxInfo != NULL) { Mtx lightMtx; - - #if TARGET_PC - C_MTXLightPerspective(lightMtx, dComIfGd_getView()->fovy, - dComIfGd_getView()->aspect, 1.0f, 1.0f, dusk::getSettings().game.useWaterProjectionOffset ? -0.01f : 0.0f, 0); - #else C_MTXLightPerspective(lightMtx, dComIfGd_getView()->fovy, dComIfGd_getView()->aspect, 1.0f, 1.0f, -0.01f, 0); - #endif - #if WIDESCREEN_SUPPORT mDoGph_gInf_c::setWideZoomLightProjection(lightMtx); #endif diff --git a/src/d/actor/d_a_obj_tp.cpp b/src/d/actor/d_a_obj_tp.cpp index e2ede47b15..2690de3e04 100644 --- a/src/d/actor/d_a_obj_tp.cpp +++ b/src/d/actor/d_a_obj_tp.cpp @@ -36,15 +36,8 @@ static int daObj_Tp_Draw(obj_tp_class* i_this) { &material->getTexGenBlock()->getTexMtx(0)->getTexMtxInfo(); if (texMtxInfo != NULL) { Mtx lightProjMtx; - - #if TARGET_PC - C_MTXLightPerspective(lightProjMtx, dComIfGd_getView()->fovy, - dComIfGd_getView()->aspect, 1.0f, 1.0f, dusk::getSettings().game.useWaterProjectionOffset ? -0.01f : 0.0f, 0); - #else C_MTXLightPerspective(lightProjMtx, dComIfGd_getView()->fovy, dComIfGd_getView()->aspect, 1.0f, 1.0f, -0.01f, 0); - #endif - #if WIDESCREEN_SUPPORT mDoGph_gInf_c::setWideZoomLightProjection(lightProjMtx); #endif diff --git a/src/d/d_kankyo.cpp b/src/d/d_kankyo.cpp index 753be77a40..0bd3db8e2a 100644 --- a/src/d/d_kankyo.cpp +++ b/src/d/d_kankyo.cpp @@ -11393,11 +11393,7 @@ void dKy_bg_MAxx_proc(void* bg_model_p) { if (mat_name[6] == '2') { C_MTXLightPerspective(sp1D8, dComIfGd_getView()->fovy, camera_p->view.aspect, 1.0f, 1.0f, -#if TARGET_PC - dusk::getSettings().game.useWaterProjectionOffset ? -0.01f : 0.0f, 0.0f); -#else -0.01f, 0.0f); -#endif } else { C_MTXLightPerspective(sp1D8, dComIfGd_getView()->fovy, camera_p->view.aspect, 0.49f, -0.49f, 0.5f, 0.5f); diff --git a/src/dusk/imgui/ImGuiMenuGame.cpp b/src/dusk/imgui/ImGuiMenuGame.cpp index ac738c6e24..d2b893c884 100644 --- a/src/dusk/imgui/ImGuiMenuGame.cpp +++ b/src/dusk/imgui/ImGuiMenuGame.cpp @@ -64,11 +64,7 @@ namespace dusk { config::ImGuiCheckbox("Native Bloom", getSettings().game.enableBloom); - config::ImGuiCheckbox("Water Projection Offset", getSettings().game.useWaterProjectionOffset); - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("Adds GC-specific -0.01 transS offset\n" - "that causes ~6px ghost artifacts in water reflections."); - } + config::ImGuiCheckbox("Enable Water Refraction", getSettings().game.enableWaterRefraction); ImGui::EndMenu(); } diff --git a/src/dusk/settings.cpp b/src/dusk/settings.cpp index 4768abef93..dabed72f19 100644 --- a/src/dusk/settings.cpp +++ b/src/dusk/settings.cpp @@ -40,7 +40,7 @@ UserSettings g_userSettings = { // Graphics .enableBloom {"game.enableBloom", true}, - .useWaterProjectionOffset {"game.useWaterProjectionOffset", false}, + .enableWaterRefraction {"game.enableWaterRefraction", true}, .enableFrameInterpolation = {"game.enableFrameInterpolation", false}, .shadowResolutionMultiplier {"game.shadowResolutionMultiplier", 1}, @@ -103,7 +103,7 @@ void registerSettings() { Register(g_userSettings.game.enableMirrorMode); Register(g_userSettings.game.invertCameraXAxis); Register(g_userSettings.game.enableBloom); - Register(g_userSettings.game.useWaterProjectionOffset); + Register(g_userSettings.game.enableWaterRefraction); Register(g_userSettings.game.shadowResolutionMultiplier); Register(g_userSettings.game.enableFastIronBoots); Register(g_userSettings.game.canTransformAnywhere);