diff --git a/README.md b/README.md index 02b1ded875..5e7412e6aa 100644 --- a/README.md +++ b/README.md @@ -34,8 +34,8 @@ First, make sure your dump of the game is clean and supported by Dusk. You can d - Extract the .zip file - Launch Dusk -- Press **Select Disc Image**, navigate to your game dump, and select the file -- Press **Start Game** to play! +- Press **Select Disc Image** and provide the path to your supported game dump. +- Press **Play**! # Building @@ -46,3 +46,10 @@ Pull requests are welcomed! Note that we do not accept contributions that are pr # Credits Special thanks to the [TP decompilation](https://github.com/zeldaret/tp) team, the GC/Wii decompilation community, the [Aurora](https://github.com/encounter/aurora) developers, the [TP speedrunning community](https://zsrtp.link), and all [contributors](https://github.com/TwilitRealm/dusk/graphs/contributors). + +
+
+ + Powered by Aurora + +
diff --git a/assets/aurora-powered.png b/assets/aurora-powered.png new file mode 100644 index 0000000000..35ff017776 Binary files /dev/null and b/assets/aurora-powered.png differ diff --git a/extern/aurora b/extern/aurora index 518747aa86..f845a5a58c 160000 --- a/extern/aurora +++ b/extern/aurora @@ -1 +1 @@ -Subproject commit 518747aa86a50be62ecf92aeb309309b0d58a54a +Subproject commit f845a5a58cfb576049e9ba8392c5c5ca61d550b6 diff --git a/files.cmake b/files.cmake index e899318cb8..56ea825685 100644 --- a/files.cmake +++ b/files.cmake @@ -1461,6 +1461,8 @@ set(DUSK_FILES src/dusk/imgui/ImGuiSaveEditor.cpp src/dusk/imgui/ImGuiStateShare.hpp src/dusk/imgui/ImGuiStateShare.cpp + src/dusk/ui/achievements.cpp + src/dusk/ui/achievements.hpp src/dusk/ui/bool_button.cpp src/dusk/ui/bool_button.hpp src/dusk/ui/button.cpp @@ -1471,16 +1473,16 @@ set(DUSK_FILES src/dusk/ui/controller_config.hpp src/dusk/ui/document.cpp src/dusk/ui/document.hpp - src/dusk/ui/achievements.cpp - src/dusk/ui/achievements.hpp - src/dusk/ui/preset.cpp - src/dusk/ui/preset.hpp src/dusk/ui/editor.cpp src/dusk/ui/editor.hpp src/dusk/ui/event.cpp src/dusk/ui/event.hpp + src/dusk/ui/graphics_tuner.cpp + src/dusk/ui/graphics_tuner.hpp src/dusk/ui/input.cpp src/dusk/ui/input.hpp + src/dusk/ui/modal.cpp + src/dusk/ui/modal.hpp src/dusk/ui/nav_types.hpp src/dusk/ui/number_button.cpp src/dusk/ui/number_button.hpp @@ -1488,12 +1490,12 @@ set(DUSK_FILES src/dusk/ui/overlay.hpp src/dusk/ui/pane.cpp src/dusk/ui/pane.hpp - src/dusk/ui/popup.cpp - src/dusk/ui/popup.hpp + src/dusk/ui/menu_bar.cpp + src/dusk/ui/menu_bar.hpp src/dusk/ui/prelaunch.cpp src/dusk/ui/prelaunch.hpp - src/dusk/ui/prelaunch_options.cpp - src/dusk/ui/prelaunch_options.hpp + src/dusk/ui/preset.cpp + src/dusk/ui/preset.hpp src/dusk/ui/select_button.cpp src/dusk/ui/select_button.hpp src/dusk/ui/settings.cpp diff --git a/include/d/d_menu_fmap2D.h b/include/d/d_menu_fmap2D.h index d0152f0229..7df78bb6d0 100644 --- a/include/d/d_menu_fmap2D.h +++ b/include/d/d_menu_fmap2D.h @@ -169,6 +169,12 @@ public: void mapBlink() {} + #if PLATFORM_WII || TARGET_PC + f32 getMirrorPosX(f32 param_0, f32 param_1) { + return (field_0x11dc * 2.0f - (param_0 + param_1)) - param_1; + } + #endif + // Unknown name struct RegionTexData { /* 0x00 */ float mMinX; diff --git a/include/d/d_menu_map_common.h b/include/d/d_menu_map_common.h index 989aee7d8b..de50d775ca 100644 --- a/include/d/d_menu_map_common.h +++ b/include/d/d_menu_map_common.h @@ -66,6 +66,16 @@ public: _c90 = param_2; } +#if PLATFORM_WII || TARGET_PC + f32 getMirrorCenterPosX(f32 param_0, f32 param_1) { + if (_c90) { + return (mCenterPosX * 2.0f - (param_0 + param_1)) - param_1; + } + + return param_0; + } +#endif + struct Stage_c { // Incomplete class diff --git a/include/dusk/achievements.h b/include/dusk/achievements.h index ca4676ab2d..bf0021c9d7 100644 --- a/include/dusk/achievements.h +++ b/include/dusk/achievements.h @@ -50,8 +50,6 @@ public: bool hasSignal(const char* key) const; std::vector getAchievements() const; - bool hasPendingUnlock() const { return !m_pendingUnlocks.empty(); } - std::string consumePendingUnlock(); private: struct Entry { @@ -68,7 +66,6 @@ private: std::unordered_set m_signals; bool m_loaded = false; bool m_dirty = false; - std::queue m_pendingUnlocks; }; } // namespace dusk diff --git a/include/dusk/main.h b/include/dusk/main.h index 065f507d36..d6b9c9927f 100644 --- a/include/dusk/main.h +++ b/include/dusk/main.h @@ -1,6 +1,10 @@ #ifndef DUSK_MAIN_H #define DUSK_MAIN_H +#if defined(__APPLE__) +#include +#endif + #include namespace dusk { @@ -8,7 +12,17 @@ namespace dusk { extern bool IsShuttingDown; extern bool IsGameLaunched; extern bool IsFocusPaused; + extern bool RestartRequested; extern std::filesystem::path ConfigPath; + +#if defined(__ANDROID__) || (defined(TARGET_OS_IOS) && TARGET_OS_IOS) || \ + (defined(TARGET_OS_TV) && TARGET_OS_TV) + inline constexpr bool SupportsProcessRestart = false; +#else + inline constexpr bool SupportsProcessRestart = true; +#endif + + void RequestRestart() noexcept; } #endif // DUSK_MAIN_H diff --git a/include/dusk/settings.h b/include/dusk/settings.h index 7f7aa8cd69..766fa506c1 100644 --- a/include/dusk/settings.h +++ b/include/dusk/settings.h @@ -67,7 +67,6 @@ struct UserSettings { // QoL ConfigVar enableQuickTransform; ConfigVar hideTvSettingsScreen; - ConfigVar skipWarningScreen; ConfigVar biggerWallets; ConfigVar noReturnRupees; ConfigVar disableRupeeCutscenes; diff --git a/res/rml/overlay.rcss b/res/rml/overlay.rcss index e18bb436e5..416252a66b 100644 --- a/res/rml/overlay.rcss +++ b/res/rml/overlay.rcss @@ -8,140 +8,107 @@ body { height: 100%; margin: 0; padding: 0; - font-family: "Fira Sans Condensed"; - font-size: 24dp; - color: #FFFFFF; - display: flex; - flex-direction: column; - justify-content: flex-end; - align-items: stretch; -} - -.overlay-root { - width: 100%; - min-height: 45%; - display: flex; - flex-direction: column; - justify-content: flex-end; - align-items: stretch; - decorator: vertical-gradient(#00000000 #151610F2); - filter: opacity(0); - transition: filter 0.2s linear-in-out; -} - -.overlay-root[open] { - filter: opacity(1); -} - -.overlay { - width: 100%; - max-width: 1216dp; - margin-left: auto; - margin-right: auto; - display: flex; - flex-direction: column; - gap: 24dp; - padding: 48dp 64dp; -} - -@media (max-height: 800dp) { - .overlay-root { - min-height: 38%; - } - - .overlay { - gap: 16dp; - padding: 32dp 48dp; - } -} - -.header { - display: flex; - justify-content: space-between; - align-items: center; - gap: 24dp; -} - -.carousel-container { - flex: 1 1 auto; - display: flex; - justify-content: flex-end; - min-width: 0; -} - -.description { - font-size: 18dp; - line-height: 22dp; - color: rgba(255, 255, 255, 50%); -} - -.divider { - margin: 1dp 0; - border-top: 1dp rgba(217, 217, 217, 50%); -} - -.footer { - display: flex; - justify-content: space-between; - align-items: center; - gap: 24dp; -} - -footer-button { - display: block; - width: 100%; - max-width: 220dp; - border: 0; - padding: 0; - background-color: transparent; - font-family: "Fira Sans Condensed"; - font-weight: bold; + font-family: "Fira Sans"; + font-weight: normal; font-size: 20dp; - line-height: 24dp; - text-transform: uppercase; - color: #FFFFFF; - opacity: 1; + color: #E0DBC8; + display: flex; + flex-direction: column; + justify-content: flex-end; + align-items: stretch; + z-index: 1; + pointer-events: none; +} + +toast { + position: absolute; + top: 40dp; + right: 40dp; + display: flex; + flex-flow: column; + border-radius: 14dp; + overflow: hidden; + border: 1dp #92875B; + backdrop-filter: blur(5dp); + box-shadow: 0 0 15dp 3dp; + background-color: rgba(21, 22, 16, 80%); + filter: opacity(0); + transform: scale(0.9); + transform-origin: center; + transition: filter transform 0.2s cubic-in-out; + padding: 18dp 24dp; + gap: 8dp; +} + +toast[open] { + filter: opacity(1); + transform: scale(1); +} + +/*toast:hover { cursor: pointer; + background-color: rgba(61, 59, 36, 80%); } -footer-button.return { - text-align: left; +toast:active { + background-color: rgba(45, 43, 26, 80%); +}*/ + +toast heading { + display: flex; + gap: 18dp; + align-items: center; + font-family: "Fira Sans Condensed"; + font-size: 18dp; + font-weight: bold; + text-transform: uppercase; + color: #92875B; } -footer-button.reset { - text-align: right; -} - -.stepped-carousel { +toast message { display: flex; align-items: center; - justify-content: center; - gap: 16dp; - width: auto; - min-width: 246dp; - padding: 0; - background-color: transparent; - font-family: "Fira Sans Condensed"; - font-weight: bold; + justify-content: start; + gap: 8dp; } -.stepped-carousel-value { - line-height: 29dp; - min-width: 166dp; - text-align: center; - white-space: nowrap; - opacity: 0.9; +toast progress { + height: 4dp; + position: absolute; + left: 0; + bottom: 0; + width: 100%; } -.stepped-carousel-arrow { - width: 24dp; - height: 24dp; - min-width: 24dp; - padding: 0; - border: 0; - background-color: transparent; - opacity: 1; - cursor: pointer; +toast progress fill { + background-color: rgba(194, 164, 45, 80%); +} + +toast.achievement { + border: 1dp #C2A42D; +} + +toast.achievement heading { + color: #C2A42D; +} + +icon { font-family: "Material Symbols Rounded"; font-weight: normal; + display: inline-block; + vertical-align: middle; } + +icon.arrow-forward { + width: 24dp; + height: 24dp; + font-size: 24dp; + decorator: text("" center center); +} + +icon.trophy { + width: 24dp; + height: 24dp; + font-size: 24dp; + decorator: text("" center center); +} \ No newline at end of file diff --git a/res/rml/prelaunch.rcss b/res/rml/prelaunch.rcss index 9556e74ef2..1c217e466c 100644 --- a/res/rml/prelaunch.rcss +++ b/res/rml/prelaunch.rcss @@ -9,17 +9,44 @@ body { font-weight: normal; font-size: 20dp; color: #FFFFFF; - background-color: #000000; - decorator: image(../prelaunch-bg.png cover left center); filter: opacity(0); transition: filter 1s 0.2s linear-in-out; z-index: -1; } +.gradient { + position: absolute; + width: 100%; + height: 100%; + /* The color gradient from the Figma bands really badly. A fully black gradient does as well, but not as badly. */ + decorator: horizontal-gradient(#000000FF #00000000); +} + +body.mirrored .gradient { + decorator: horizontal-gradient(#00000000 #000000FF); +} + +.background { + position: absolute; + width: 100%; + height: 100%; + decorator: image(../prelaunch-bg.png cover left center); + opacity: 0; + transition: opacity 1s linear-in-out; +} + body[open] { filter: opacity(1); } +body[open] .background { + opacity: 1; +} + +body.disc-ready .background { + opacity: 0; +} + content { display: block; width: 100%; @@ -35,6 +62,7 @@ content[open] { menu { position: absolute; left: 96dp; + right: auto; top: 50%; transform: translateY(-50%); /* Scale based on a reference screen width, 428/1216 */ @@ -47,6 +75,11 @@ menu { gap: 48dp; } +body.mirrored menu { + left: auto; + right: 96dp; +} + hero { display: flex; flex-direction: column; @@ -55,6 +88,10 @@ hero { gap: 8dp; } +body.mirrored hero { + align-items: flex-end; +} + hero img { width: 100%; } @@ -79,6 +116,7 @@ hero img { display: flex; flex-direction: column; gap: 12dp; + align-items: flex-start; } #menu-list button { @@ -86,6 +124,7 @@ hero img { height: 54dp; padding: 8dp 16dp; border-radius: 8dp; + text-align: left; text-transform: uppercase; font-family: "Fira Sans Condensed"; font-size: 32dp; @@ -105,25 +144,56 @@ hero img { decorator: horizontal-gradient(#FEE685FF #FEE68500); } +body.mirrored #menu-list { + align-items: flex-end; +} + +body.mirrored #menu-list button { + text-align: right; +} + +body.mirrored #menu-list button:hover, +body.mirrored #menu-list button:focus-visible { + decorator: horizontal-gradient(#FEE68500 #FEE685FF); +} + disc-info { position: absolute; left: 96dp; + right: auto; bottom: 72dp; display: flex; flex-direction: column; gap: 12dp; font-size: 24dp; + font-effect: glow(0dp 4dp 0dp 4dp black); + text-align: left; +} + +body.mirrored disc-info { + left: auto; + right: 96dp; + text-align: right; } version-info { position: absolute; right: 96dp; + left: auto; bottom: 72dp; display: flex; flex-direction: column; gap: 12dp; text-align: right; font-size: 24dp; + font-effect: glow(0dp 4dp 0dp 4dp black); + text-align: right; +} + +body.mirrored version-info { + right: auto; + left: 96dp; + text-align: left; } #disc-status { diff --git a/res/rml/tuner.rcss b/res/rml/tuner.rcss new file mode 100644 index 0000000000..86dd2043f6 --- /dev/null +++ b/res/rml/tuner.rcss @@ -0,0 +1,147 @@ +*, *:before, *:after { + box-sizing: border-box; +} + +body { + overflow: visible; + width: 100%; + height: 100%; + margin: 0; + padding: 0; + font-family: "Fira Sans Condensed"; + font-size: 24dp; + color: #FFFFFF; + display: flex; + flex-direction: column; + justify-content: flex-end; + align-items: stretch; +} + +.tuner-root { + width: 100%; + min-height: 45%; + display: flex; + flex-direction: column; + justify-content: flex-end; + align-items: stretch; + decorator: vertical-gradient(#00000000 #151610F2); + filter: opacity(0); + transition: filter 0.2s linear-in-out; +} + +.tuner-root[open] { + filter: opacity(1); +} + +.tuner { + width: 100%; + max-width: 1216dp; + margin-left: auto; + margin-right: auto; + display: flex; + flex-direction: column; + gap: 24dp; + padding: 48dp 64dp; +} + +@media (max-height: 800dp) { + .tuner-root { + min-height: 38%; + } + + .tuner { + gap: 16dp; + padding: 32dp 48dp; + } +} + +.header { + display: flex; + justify-content: space-between; + align-items: center; + gap: 24dp; +} + +.carousel-container { + flex: 1 1 auto; + display: flex; + justify-content: flex-end; + min-width: 0; +} + +.description { + font-size: 18dp; + line-height: 22dp; + color: rgba(255, 255, 255, 50%); +} + +.divider { + margin: 1dp 0; + border-top: 1dp rgba(217, 217, 217, 50%); +} + +.footer { + display: flex; + justify-content: space-between; + align-items: center; + gap: 24dp; +} + +footer-button { + display: block; + width: 100%; + max-width: 220dp; + border: 0; + padding: 0; + background-color: transparent; + font-family: "Fira Sans Condensed"; + font-weight: bold; + font-size: 20dp; + line-height: 24dp; + text-transform: uppercase; + color: #FFFFFF; + opacity: 1; + cursor: pointer; +} + +footer-button.return { + text-align: left; +} + +footer-button.reset { + text-align: right; +} + +.stepped-carousel { + display: flex; + align-items: center; + justify-content: center; + gap: 16dp; + width: auto; + min-width: 246dp; + padding: 0; + background-color: transparent; + font-family: "Fira Sans Condensed"; + font-weight: bold; +} + +.stepped-carousel-value { + line-height: 29dp; + min-width: 166dp; + text-align: center; + white-space: nowrap; + opacity: 0.9; +} + +.stepped-carousel-arrow { + width: 24dp; + height: 24dp; + min-width: 24dp; + padding: 0; + border: 0; + background-color: transparent; + opacity: 1; + cursor: pointer; + font-family: "Material Symbols Rounded"; + font-weight: normal; +} diff --git a/res/rml/window.rcss b/res/rml/window.rcss index db3558778a..c35670904e 100644 --- a/res/rml/window.rcss +++ b/res/rml/window.rcss @@ -43,6 +43,10 @@ window.preset { min-width: 650dp; } +window.modal { + max-width: 816dp; +} + window[open] { filter: opacity(1); transform: scale(1); @@ -108,6 +112,12 @@ window content pane > spacer { pointer-events: none; } +window modal { + padding: 32dp; + gap: 20dp; + flex: 0 1 auto; +} + scrollbarvertical { width: 8dp; margin: 4dp 4dp 4dp 0; @@ -194,6 +204,12 @@ button:not(:disabled):active { box-shadow: #C2A42D 0 0 0 2dp; } +button.modal-btn { + font-size: 20dp; + padding: 16dp 10dp; + flex: 1 1 0; +} + select-button { display: flex; align-items: center; @@ -399,3 +415,22 @@ button.preset-btn { color: rgba(224, 219, 200, 65%); text-align: center; } + +.modal-dialog { + display: flex; + flex-flow: column; + padding: 16dp; + gap: 20dp; + flex: 0 1 auto; + min-width: 0; +} + +.modal-actions { + display: flex; + flex-direction: row; + flex-wrap: nowrap; + align-items: stretch; + gap: 12dp; + padding-top: 12dp; + width: 100%; +} diff --git a/src/d/actor/d_a_alink_horse.inc b/src/d/actor/d_a_alink_horse.inc index ac7ce73e50..f0c585f1b8 100644 --- a/src/d/actor/d_a_alink_horse.inc +++ b/src/d/actor/d_a_alink_horse.inc @@ -2721,7 +2721,7 @@ int daAlink_c::procHorseRun() { } if (mProcVar2.field_0x300c == 0) { - set3DStatus(BUTTON_STATUS_HOLD_ON, 4); + set3DStatus(BUTTON_STATUS_HOLD_ON, IF_DUSK(dusk::getSettings().game.enableMirrorMode ? 1 :) 4); } } else { if (mProcVar3.field_0x300e != 0) { @@ -2731,7 +2731,7 @@ int daAlink_c::procHorseRun() { } if (mProcVar2.field_0x300c == 0) { - set3DStatus(BUTTON_STATUS_HOLD_ON, 1); + set3DStatus(BUTTON_STATUS_HOLD_ON, IF_DUSK(dusk::getSettings().game.enableMirrorMode ? 4 :) 1); } } diff --git a/src/d/d_demo.cpp b/src/d/d_demo.cpp index d6425848d3..2f099f2c2a 100644 --- a/src/d/d_demo.cpp +++ b/src/d/d_demo.cpp @@ -11,6 +11,62 @@ #include "JSystem/JGadget/define.h" #include +#include "dusk/logging.h" + +#if TARGET_PC +#include "dusk/ui/ui.hpp" + +namespace { +static int sJaiSkip = -1; + +static JSUList* get_stream_list() { + return Z2GetSoundMgr()->getStreamMgr()->getStreamList(); +} + +static int get_stream_count(JSUList* list) { + int i = 0; + for (JSULink* l = list != nullptr ? list->getFirst() : nullptr; l != nullptr; + l = l->getNext()) { + i++; + } + return i; +} + +static void pause_stream(int skip_first, bool paused) { + int i = 0; + JSUList* list = get_stream_list(); + for (JSULink* l = list->getFirst(); l != nullptr; l = l->getNext(), ++i) { + if (i >= skip_first) { + l->getObject()->pause(paused); + } + } +} + +static void pause_streams(int skip_first) { + if (!dusk::ui::is_prelaunch_open()) { + return; + } + JSUList* list = get_stream_list(); + if (list == nullptr || get_stream_count(list) <= skip_first) { + return; + } + pause_stream(skip_first, true); + sJaiSkip = skip_first; +} + +static void unpause_streams(bool require_prelaunch_hidden) { + if (sJaiSkip < 0) { + return; + } + if (require_prelaunch_hidden && dusk::ui::is_prelaunch_open()) { + return; + } + pause_stream(sJaiSkip, false); + sJaiSkip = -1; +} +} // namespace +#endif + s16 dDemo_c::m_branchId = -1; namespace { @@ -1006,7 +1062,16 @@ int dDemo_c::start(u8 const* p_data, cXyz* p_translation, f32 rotationY) { m_control->setSuspend(0); } +#if TARGET_PC + const int existing_streams = get_stream_count(get_stream_list()); +#endif + m_control->forward(0); + +#if TARGET_PC + pause_streams(existing_streams); +#endif + m_translation = p_translation; if (m_translation != NULL) { @@ -1034,6 +1099,10 @@ static void dummyString2() { void dDemo_c::end() { JUT_ASSERT(1956, m_system != NULL); +#if TARGET_PC + unpause_streams(false); +#endif + m_control->destroyObject_all(); m_object->remove(); m_data = NULL; @@ -1054,6 +1123,10 @@ void dDemo_c::branch() { int dDemo_c::update() { JUT_ASSERT(2064, m_system != NULL); +#if TARGET_PC + unpause_streams(true); +#endif + if (m_data == NULL) { if (m_branchData == NULL) { return 0; diff --git a/src/d/d_menu_fmap.cpp b/src/d/d_menu_fmap.cpp index 149e03349d..d2b06e962c 100644 --- a/src/d/d_menu_fmap.cpp +++ b/src/d/d_menu_fmap.cpp @@ -919,9 +919,20 @@ void dMenu_Fmap_c::region_map_proc() { } mpDraw2DBack->regionMapMove(mpStick); int stage_no, room_no; + +#if TARGET_PC + f32 arrow_pos_x = mpDraw2DBack->getArrowPos2DX(); + if (dusk::getSettings().game.enableMirrorMode) { + arrow_pos_x = mpDraw2DBack->getMirrorPosX(arrow_pos_x, 0.0f); + } + + f32 pos_x = arrow_pos_x - mDoGph_gInf_c::getMinXF() - mDoGph_gInf_c::getWidthF() * 0.5f; +#else f32 pos_x = mpDraw2DBack->getArrowPos2DX() - mDoGph_gInf_c::getMinXF() - mDoGph_gInf_c::getWidthF() * 0.5f; +#endif f32 pos_y = mpDraw2DBack->getArrowPos2DY() - mDoGph_gInf_c::getHeightF() * 0.5f; + mpMenuFmapMap->getPointStagePathInnerNo(getNowFmapRegionData(), pos_x, pos_y, mStayStageNo, &stage_no, &room_no); if (mStageCursor != stage_no || mRoomCursor != room_no || mResetAreaName) { @@ -2464,6 +2475,13 @@ void dMenu_Fmap_c::portalWarpMapMove(STControl* i_stick) { f32 arrow_y = mpDraw2DBack->getArrowPos2DY(); u8 uVar6 = 0xff; +#if TARGET_PC + if (dusk::getSettings().game.enableMirrorMode) { + arrow_x = mpDraw2DBack->getMirrorPosX(arrow_x, 0.0f); + } +#endif + + for (int i = 0; i < portal_dat->mCount; i++) { if (portals[i].mRegionNo == mpDraw2DBack->getRegionCursor() + 1 && checkDrawPortalIcon(portals[i].mStageNo, portals[i].mSwitchNo)) diff --git a/src/d/d_menu_fmap2D.cpp b/src/d/d_menu_fmap2D.cpp index ea9912998b..ffec184d4e 100644 --- a/src/d/d_menu_fmap2D.cpp +++ b/src/d/d_menu_fmap2D.cpp @@ -1043,6 +1043,12 @@ void dMenu_Fmap2DBack_c::allmap_move2(STControl* param_0) { calcAllMapPos2D((mArrowPos3DX + control_xpos) - mStageTransX, (mArrowPos3DZ + control_ypos) - mStageTransZ, &sp14, &sp10); +#if TARGET_PC + if (dusk::getSettings().game.enableMirrorMode) { + sp14 = getMirrorPosX(sp14, 0.0f); + } +#endif + mSelectRegion = 0xff; for (int i = 7; i >= 0; i--) { int val = field_0x1230[i]; @@ -1397,6 +1403,15 @@ void dMenu_Fmap2DBack_c::regionTextureDraw() { if (uVar10 != uVar9) { bool b = 0; f32 v = mTransX + (dVar14 + (mRegionMinMapX[uVar10] + field_0xf0c[uVar10])); + + #if TARGET_PC + if (dusk::getSettings().game.enableMirrorMode) { + b = true; + v = getMirrorPosX(mTransX + (dVar14 + (mRegionMinMapX[uVar10] + field_0xf0c[uVar10])), + mRegionMapSizeX[uVar10] * mZoom * 0.5f); + } + #endif + mpAreaTex[uVar10]->draw( v, mTransZ + (dVar13 + (mRegionMinMapY[uVar10] + field_0xf2c[uVar10])), mRegionMapSizeX[uVar10] * mZoom, mRegionMapSizeY[uVar10] * mZoom, b, false, @@ -1404,6 +1419,15 @@ void dMenu_Fmap2DBack_c::regionTextureDraw() { } else { bool b = 0; f32 v = mTransX + (dVar14 + (mRegionMinMapX[uVar9] + field_0xf0c[uVar9])); + + #if TARGET_PC + if (dusk::getSettings().game.enableMirrorMode) { + b = true; + v = getMirrorPosX(mTransX + (dVar14 + (mRegionMinMapX[uVar9] + field_0xf0c[uVar9])), + mRegionMapSizeX[uVar9] * mZoom * 0.5f); + } + #endif + mpAreaTex[uVar9]->draw( v, mTransZ + (dVar13 + (mRegionMinMapY[uVar9] + field_0xf2c[uVar9])), mRegionMapSizeX[uVar9] * mZoom, mRegionMapSizeY[uVar9] * mZoom, b, false, diff --git a/src/d/d_menu_map_common.cpp b/src/d/d_menu_map_common.cpp index e7d0fef7ad..0017975c4e 100644 --- a/src/d/d_menu_map_common.cpp +++ b/src/d/d_menu_map_common.cpp @@ -343,6 +343,11 @@ void dMenuMapCommon_c::drawIcon(f32 i_posX, f32 i_posY, f32 param_3, f32 param_4 } f32 pos_x = icon_pos_x + i_posX; + #if TARGET_PC + if (dusk::getSettings().game.enableMirrorMode) { + pos_x = getMirrorCenterPosX(pos_x, 0.0f); + } + #endif mpDrawCursor->setPos(pos_x, icon_pos_y + i_posY); mpDrawCursor->setScale(mIconInfo[info_idx].scale * g_fmapHIO.mMapIconHIO.mPortalCursorScale); mpDrawCursor->draw(); @@ -364,6 +369,12 @@ void dMenuMapCommon_c::drawIcon(f32 i_posX, f32 i_posY, f32 param_3, f32 param_4 } f32 pos_x = (icon_pos_x + i_posX); + #if TARGET_PC + if (dusk::getSettings().game.enableMirrorMode) { + pos_x = getMirrorCenterPosX(pos_x, 0.0f); + } + #endif + mpPortalIcon->setPos(pos_x, icon_pos_y + i_posY); mpPortalIcon->setScale(mIconInfo[info_idx].scale * g_fmapHIO.mMapIconHIO.mPortalIconScale); mpPortalIcon->draw(); @@ -399,6 +410,12 @@ void dMenuMapCommon_c::drawIcon(f32 i_posX, f32 i_posY, f32 param_3, f32 param_4 } f32 pos_x = i_posX + (icon_pos_x - (icon_size_x / 2)); + #if TARGET_PC + if (dusk::getSettings().game.enableMirrorMode) { + pos_x = getMirrorCenterPosX(i_posX + (icon_pos_x - (icon_size_x / 2)), icon_size_x / 2); + } + #endif + mPictures[mIconInfo[info_idx].icon_no]->draw(pos_x, (i_posY + (icon_pos_y - icon_size_y / 2)), icon_size_x, icon_size_y, false, false, false); diff --git a/src/d/d_msg_object.cpp b/src/d/d_msg_object.cpp index 5e520753c9..3313534de3 100644 --- a/src/d/d_msg_object.cpp +++ b/src/d/d_msg_object.cpp @@ -431,6 +431,16 @@ static void dummyStrings() { dMsgObject_HIO_c g_MsgObject_HIO_c; int dMsgObject_c::_execute() { +#if TARGET_PC + if (dusk::getSettings().game.enableMirrorMode) { + // enable wii message index override + g_MsgObject_HIO_c.mMessageDisplay = 1; + } else if (!dusk::getSettings().game.enableMirrorMode && g_MsgObject_HIO_c.mMessageDisplay == 1) { + g_MsgObject_HIO_c.mMessageDisplay = 0; + } +#endif + + field_0x4c7 = 0; if (mpTalkHeap != NULL) { field_0x148 = mDoExt_setCurrentHeap(mpTalkHeap); diff --git a/src/d/d_s_logo.cpp b/src/d/d_s_logo.cpp index cc5d0114a4..db0cfa1387 100644 --- a/src/d/d_s_logo.cpp +++ b/src/d/d_s_logo.cpp @@ -1120,26 +1120,12 @@ int dScnLogo_c::create() { checkProgSelect(); if (field_0x20a != 0) { mExecCommand = EXEC_PROG_IN; - #if TARGET_PC - mTimer = dusk::getSettings().game.skipWarningScreen ? 1 : 30; - #else mTimer = 30; - #endif field_0x218 = getProgressiveMode(); } else { #if TARGET_PC - if (dusk::getSettings().game.skipWarningScreen) { - mTimer = 0; // Possibly unnecessary but just in case - mExecCommand = EXEC_DVD_WAIT; - } else { - if (mDoRst::getWarningDispFlag()) { - mTimer = 90; - mExecCommand = EXEC_NINTENDO_IN; - } else { - mTimer = 120; - mExecCommand = EXEC_WARNING_IN; - } - } + mTimer = 0; // Possibly unnecessary but just in case + mExecCommand = EXEC_DVD_WAIT; #else if (mDoRst::getWarningDispFlag()) { mTimer = 90; diff --git a/src/d/d_s_play.cpp b/src/d/d_s_play.cpp index 68d788d1f4..3b38b6c8a5 100644 --- a/src/d/d_s_play.cpp +++ b/src/d/d_s_play.cpp @@ -40,9 +40,11 @@ #include "JSystem/JKernel/JKRAramArchive.h" #if TARGET_PC +#include "dusk/autosave.h" #include "dusk/memory.h" #include "dusk/randomizer/game/tools.h" #include +#include "dusk/ui/ui.hpp" #endif #if DEBUG @@ -795,7 +797,17 @@ static int dScnPly_Execute(dScnPly_c* i_this) { dJprev_c::get()->update(); #endif +#if TARGET_PC + if (!dusk::ui::is_prelaunch_open()) { + dDemo_c::update(); + } else if (dusk::getSettings().audio.menuSounds) { + s8 reverb = dComIfGp_getReverb(dComIfGp_roomControl_getStayNo()); + f32 fxMix = reverb / 127.0f; + g_mEnvSeMgr.field_0x144.startEnvSeDirLevel(JA_SE_ATM_WIND_1, fxMix, 1.0f); + } +#else dDemo_c::update(); +#endif #if DEBUG dJcame_c::get()->update(); diff --git a/src/dusk/achievements.cpp b/src/dusk/achievements.cpp index 26a302678e..a090a40969 100644 --- a/src/dusk/achievements.cpp +++ b/src/dusk/achievements.cpp @@ -1,14 +1,15 @@ #include "dusk/achievements.h" -#include "dusk/io.hpp" -#include "dusk/main.h" -#include "d/d_com_inf_game.h" -#include "d/d_meter2_info.h" #include "d/actor/d_a_alink.h" #include "d/actor/d_a_npc4.h" #include "d/actor/d_a_player.h" +#include "d/d_com_inf_game.h" #include "d/d_demo.h" -#include "f_pc/f_pc_name.h" +#include "d/d_meter2_info.h" +#include "dusk/io.hpp" +#include "dusk/main.h" +#include "dusk/ui/ui.hpp" #include "f_op/f_op_actor_mng.h" +#include "f_pc/f_pc_name.h" #include #include @@ -454,12 +455,6 @@ AchievementSystem& AchievementSystem::get() { return instance; } -std::string AchievementSystem::consumePendingUnlock() { - std::string msg = std::move(m_pendingUnlocks.front()); - m_pendingUnlocks.pop(); - return msg; -} - std::vector AchievementSystem::getAchievements() const { std::vector result; result.reserve(m_entries.size()); @@ -559,7 +554,14 @@ void AchievementSystem::processEntry(Entry& e) { if (nowUnlocked) { e.achievement.progress = e.achievement.isCounter ? e.achievement.goal : 1; e.achievement.unlocked = true; - m_pendingUnlocks.push(e.achievement.name); + if (getSettings().game.enableAchievementNotifications) { + ui::push_toast({ + .type = "achievement", + .title = "Achievement Unlocked!", + .content = e.achievement.name, + .duration = std::chrono::seconds(5), + }); + } m_dirty = true; } else if (progressChanged) { m_dirty = true; diff --git a/src/dusk/imgui/ImGuiConsole.cpp b/src/dusk/imgui/ImGuiConsole.cpp index 68c11efb10..166461e19f 100644 --- a/src/dusk/imgui/ImGuiConsole.cpp +++ b/src/dusk/imgui/ImGuiConsole.cpp @@ -10,11 +10,9 @@ #include "fmt/format.h" #include "ImGuiConsole.hpp" -#include "dusk/ui/preset.hpp" #include "dusk/ui/ui.hpp" #include "JSystem/JUtility/JUTGamePad.h" #include "SDL3/SDL_mouse.h" -#include "dusk/achievements.h" #include "dusk/audio/DuskAudioSystem.h" #include "dusk/config.hpp" #include "dusk/dusk.h" @@ -254,15 +252,6 @@ namespace dusk { UpdateSettings(); - AchievementSystem::get().tick(); - while (AchievementSystem::get().hasPendingUnlock()) { - if (getSettings().game.enableAchievementNotifications) { - m_menuTools.notifyAchievement(AchievementSystem::get().consumePendingUnlock()); - } else { - AchievementSystem::get().consumePendingUnlock(); - } - } - if (!fpcM_SearchByName(fpcNm_LOGO_SCENE_e) && (ImGui::IsKeyDown(ImGuiKey_LeftCtrl) || ImGui::IsKeyDown(ImGuiKey_RightCtrl)) && ImGui::IsKeyPressed(ImGuiKey_R)) @@ -274,10 +263,6 @@ namespace dusk { ImGuiMenuGame::ToggleFullscreen(); } - // if (!dusk::IsGameLaunched) { - // m_preLaunchWindow.draw(); - // } - if (ImGui::GetIO().KeyShift && ImGui::IsKeyPressed(ImGuiKey_F1)) { m_isHidden = !m_isHidden; } @@ -345,7 +330,6 @@ namespace dusk { } m_menuRandomizer.windowRandoStats(); m_menuRandomizer.windowRandoGeneration(); - m_menuTools.showAchievementNotification(); DuskDebugPad(); // temporary, remove later // Hide mouse cursor if the F1 menu is not open and the cursor is idle for 3 seconds. diff --git a/src/dusk/imgui/ImGuiConsole.hpp b/src/dusk/imgui/ImGuiConsole.hpp index 73b52f04c1..e4992b2544 100644 --- a/src/dusk/imgui/ImGuiConsole.hpp +++ b/src/dusk/imgui/ImGuiConsole.hpp @@ -10,7 +10,6 @@ #include "ImGuiMenuGame.hpp" #include "ImGuiMenuTools.hpp" #include "ImGuiMenuRandomizer.hpp" -#include "ImGuiPreLaunchWindow.hpp" #include "imgui.h" union SDL_Event; @@ -47,7 +46,6 @@ private: ImGuiMenuGame m_menuGame; ImGuiMenuRandomizer m_menuRandomizer; - ImGuiPreLaunchWindow m_preLaunchWindow; // Keep always last ImGuiMenuTools m_menuTools; diff --git a/src/dusk/imgui/ImGuiMenuTools.cpp b/src/dusk/imgui/ImGuiMenuTools.cpp index 714add87c4..d3c062e0f0 100644 --- a/src/dusk/imgui/ImGuiMenuTools.cpp +++ b/src/dusk/imgui/ImGuiMenuTools.cpp @@ -267,66 +267,4 @@ namespace dusk { ImGui::End(); ImGui::PopFont(); } - - void ImGuiMenuTools::notifyAchievement(std::string name) { - if (m_notifyTimer <= 0.f) { - m_notifyName = std::move(name); - m_notifyTimer = NOTIFY_DURATION; - } else { - m_notifyQueue.push(std::move(name)); - } - } - - void ImGuiMenuTools::showAchievementNotification() { - if (!getSettings().game.enableAchievementNotifications.getValue()) { - return; - } - if (m_notifyTimer <= 0.f) { - if (m_notifyQueue.empty()) { - return; - } - m_notifyName = std::move(m_notifyQueue.front()); - m_notifyQueue.pop(); - m_notifyTimer = NOTIFY_DURATION; - } - - m_notifyTimer -= ImGui::GetIO().DeltaTime; - - const float alpha = std::min({ - m_notifyTimer / NOTIFY_FADE_TIME, - (NOTIFY_DURATION - m_notifyTimer) / NOTIFY_FADE_TIME, - 1.0f - }); - - const ImGuiViewport* viewport = ImGui::GetMainViewport(); - const float padding = 12.0f; - ImGui::SetNextWindowPos( - ImVec2(viewport->WorkPos.x + viewport->WorkSize.x - padding, viewport->WorkPos.y + padding), - ImGuiCond_Always, ImVec2(1.0f, 0.0f) - ); - - ImGui::SetNextWindowBgAlpha(alpha * 0.92f); - ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(0.08f, 0.06f, 0.01f, alpha * 0.92f)); - ImGui::PushStyleColor(ImGuiCol_Border, ImVec4(1.0f, 0.8f, 0.1f, alpha)); - ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 1.0f, 1.0f, alpha)); - ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 2.0f); - ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(14.0f, 10.0f)); - - constexpr ImGuiWindowFlags flags = - ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_AlwaysAutoResize | - ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoFocusOnAppearing | - ImGuiWindowFlags_NoNav | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoInputs; - - if (ImGui::Begin("##achievement_notify", nullptr, flags)) { - ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 0.82f, 0.1f, alpha)); - ImGui::TextUnformatted("Achievement Unlocked!"); - ImGui::PopStyleColor(); - ImGui::Spacing(); - ImGui::TextUnformatted(m_notifyName.c_str()); - } - ImGui::End(); - - ImGui::PopStyleVar(2); - ImGui::PopStyleColor(3); - } } diff --git a/src/dusk/imgui/ImGuiMenuTools.hpp b/src/dusk/imgui/ImGuiMenuTools.hpp index 7ae0e5bec6..f2c68494f7 100644 --- a/src/dusk/imgui/ImGuiMenuTools.hpp +++ b/src/dusk/imgui/ImGuiMenuTools.hpp @@ -27,8 +27,6 @@ namespace dusk { void ShowAudioDebug(); void ShowSaveEditor(); void ShowStateShare(); - void notifyAchievement(std::string name); - void showAchievementNotification(); private: bool m_showDebugOverlay = false; @@ -68,12 +66,6 @@ namespace dusk { bool m_showStateShare = false; ImGuiStateShare m_stateShare; - - std::string m_notifyName; - float m_notifyTimer = 0.f; - std::queue m_notifyQueue; - static constexpr float NOTIFY_DURATION = 4.0f; - static constexpr float NOTIFY_FADE_TIME = 0.5f; }; } diff --git a/src/dusk/imgui/ImGuiPreLaunchWindow.cpp b/src/dusk/imgui/ImGuiPreLaunchWindow.cpp deleted file mode 100644 index a0006f0ad3..0000000000 --- a/src/dusk/imgui/ImGuiPreLaunchWindow.cpp +++ /dev/null @@ -1,282 +0,0 @@ -#include "imgui.h" - -#include "ImGuiConfig.hpp" -#include "ImGuiEngine.hpp" -#include "ImGuiPreLaunchWindow.hpp" - -#include "../file_select.hpp" -#include "../iso_validate.hpp" -#include "ImGuiConsole.hpp" -#include "dusk/main.h" -#include "dusk/settings.h" - -#include -#include - -#include "aurora/lib/internal.hpp" -#include "aurora/lib/window.hpp" - -namespace dusk { - -typedef void (ImGuiPreLaunchWindow::*drawFunc)(); - -drawFunc drawTable[2] = {&ImGuiPreLaunchWindow::drawMainMenu, &ImGuiPreLaunchWindow::drawOptions}; - -static constexpr std::array skLanguageNames = { - "English", "German", "French", "Spanish", "Italian" -}; - -static constexpr std::array skGameDiscFileFilters{{ - {"Game Disc Images", "iso;gcm;ciso;gcz;nfs;rvz;wbfs;wia;tgc"}, - {"All Files", "*"}, -}}; - -static std::string ShowIsoInvalidError(const iso::ValidationError code) { - using namespace std::literals::string_literals; - - switch (code) { - case iso::ValidationError::IOError: - return "Unknown IO error occurred"s; - case iso::ValidationError::InvalidImage: - return "Unable to interpret selected file as a disc image"s; - case iso::ValidationError::WrongGame: - return "Selected disc image is for a different game"s; - case iso::ValidationError::WrongVersion: - return "Selected disc image is for an unsupported version of the game. Only North American GameCube (NTSC/GZ2E01) is supported at this time."s; - case iso::ValidationError::ExecutableMismatch: - return "Selected disc image contains modified executable files."s; - default: - return "Unknown error"s; - } -} - -static std::string_view card_type_name(CARDFileType type) { - switch (type) { - case CARD_GCIFOLDER: - return "GCI Folder"sv; - case CARD_RAWIMAGE: - return "Card Image"sv; - default: - return ""sv; - } -} - -void fileDialogCallback(void* userdata, const char* path, const char* error) { - auto* self = static_cast(userdata); - if (error != nullptr) { - self->m_selectedIsoPath.clear(); - self->m_errorString = fmt::format("File dialog error: {}", error); - return; - } - - if (path == nullptr) { - self->m_selectedIsoPath.clear(); - return; - } - - self->m_selectedIsoPath = path; - self->m_isPal = iso::isPal(path); - getSettings().backend.isoPath.setValue(self->m_selectedIsoPath); - config::Save(); -} - -ImGuiPreLaunchWindow::ImGuiPreLaunchWindow() = default; - -bool ImGuiPreLaunchWindow::isSelectedPathValid() const { -#if TARGET_ANDROID - return !m_selectedIsoPath.empty(); // unsure why SDL_GetPathInfo doesnt work here -#else - return !m_selectedIsoPath.empty() && SDL_GetPathInfo(m_selectedIsoPath.c_str(), nullptr); -#endif -} - -void ImGuiPreLaunchWindow::draw() { - if (m_IsFirstDraw) { - m_selectedIsoPath = getSettings().backend.isoPath; - m_isPal = !m_selectedIsoPath.empty() && iso::isPal(m_selectedIsoPath.c_str()); - m_initialGraphicsBackend = getSettings().backend.graphicsBackend; - m_IsFirstDraw = false; - } - - if (isSelectedPathValid() && getSettings().backend.skipPreLaunchUI) { - dusk::IsGameLaunched = true; - return; - } - - auto& io = ImGui::GetIO(); - - ImGui::SetNextWindowSize(ImVec2(io.DisplaySize.x, io.DisplaySize.y)); - ImGui::SetNextWindowPos(ImVec2(0, 0)); - ImGui::SetNextWindowBgAlpha(0.65f); - - ImGui::Begin("Pre Launch Window", nullptr, - ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoResize | - ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoFocusOnAppearing | - ImGuiWindowFlags_NoBringToFrontOnFocus); - - const auto& windowSize = ImGui::GetWindowSize(); - - for (int i = 0; i < 5; i++) - ImGui::NewLine(); - - float iconSize = 150.f; - ImGui::SameLine(windowSize.x / 2 - iconSize + (iconSize / 2)); - if (ImGuiEngine::orgIcon != 0) { - ImGui::Image(ImGuiEngine::orgIcon, ImVec2{iconSize, iconSize}); - } - ImGuiTextCenter("Twilit Realm presents"); - if (ImGuiEngine::duskLogo) { - ImGui::NewLine(); - float width = iconSize * 2.5f; - ImGui::SameLine(windowSize.x / 2 - width + (width / 2)); - ImGui::Image(ImGuiEngine::duskLogo, ImVec2{width, iconSize}); - } else { - ImGui::PushFont(ImGuiEngine::fontExtraLarge); - ImGuiTextCenter("Dusk"); - ImGui::PopFont(); - } - - (this->*drawTable[m_CurMenu])(); - - ImGui::End(); -} - -void ImGuiPreLaunchWindow::drawMainMenu() { - const auto& windowSize = ImGui::GetWindowSize(); - ImGui::SetCursorPosY(windowSize.y - 200); - - ImGui::PushFont(ImGuiEngine::fontLarge); - - if (!isSelectedPathValid()) { - if (!m_errorString.empty()) { - ImGuiTextCenter(m_errorString); - } - - if (ImGuiButtonCenter("Select disc image...")) { - ShowFileSelect(&fileDialogCallback, this, aurora::window::get_sdl_window(), - skGameDiscFileFilters.data(), int(skGameDiscFileFilters.size()), nullptr, - false); - } - } else { - if (ImGuiButtonCenter("Start game")) { - dusk::IsGameLaunched = true; - } - } - - if (ImGuiButtonCenter("Options")) { - m_CurMenu = 1; - } - - ImGui::PopFont(); -} - -void ImGuiPreLaunchWindow::drawOptions() { - const auto& windowSize = ImGui::GetWindowSize(); - - ImGui::NewLine(); - - ImGui::PushFont(ImGuiEngine::fontLarge); - ImGuiTextCenter("Options"); - ImGui::Separator(); - ImGui::PopFont(); - - auto cursorY = ImGui::GetCursorPosY(); - float endCursorY = windowSize.y - 100; - - float childWidth = windowSize.x - 400; - - ImGui::SetCursorPosX(windowSize.x / 2 - (childWidth / 2)); - if (ImGui::BeginChild("OptionsChild", ImVec2(childWidth, endCursorY - cursorY), - ImGuiChildFlags_None, ImGuiWindowFlags_NoBackground)) - { - if (!m_errorString.empty()) { - ImGuiTextCenter(m_errorString); - } - - ImGui::InputText("Game ISO Path", &m_selectedIsoPath, ImGuiInputTextFlags_ReadOnly); - ImGui::SameLine(); - if (ImGui::Button(m_selectedIsoPath == "" ? "Set" : "Change")) { - ShowFileSelect(&fileDialogCallback, this, aurora::window::get_sdl_window(), - skGameDiscFileFilters.data(), int(skGameDiscFileFilters.size()), nullptr, - false); - } - - if (m_isPal) { - auto selectedLanguage = getSettings().game.language.getValue(); - if (ImGui::BeginCombo("Language", skLanguageNames[static_cast(selectedLanguage)])) { - for (u8 i = 0; i < skLanguageNames.size(); ++i) { - if (ImGui::Selectable(skLanguageNames[i])) { - getSettings().game.language.setValue(static_cast(i)); - config::Save(); - } - } - - ImGui::EndCombo(); - } - } - - AuroraBackend configuredBackend = BACKEND_AUTO; - const std::string& configuredBackendId = getSettings().backend.graphicsBackend; - if (!try_parse_backend(configuredBackendId, configuredBackend)) { - configuredBackend = BACKEND_AUTO; - } - - if (ImGui::BeginCombo("Graphics Backend", backend_name(configuredBackend).data())) { - if (ImGui::Selectable("Auto", configuredBackend == BACKEND_AUTO)) { - getSettings().backend.graphicsBackend.setValue("auto"); - config::Save(); - } - - size_t backendCount = 0; - const AuroraBackend* availableBackends = aurora_get_available_backends(&backendCount); - for (size_t i = 0; i < backendCount; ++i) { - const AuroraBackend backend = availableBackends[i]; - const bool isSelected = configuredBackend == backend; - if (ImGui::Selectable(backend_name(backend).data(), isSelected)) { - getSettings().backend.graphicsBackend.setValue( - std::string(backend_id(backend))); - config::Save(); - } - if (isSelected) { - ImGui::SetItemDefaultFocus(); - } - } - - ImGui::EndCombo(); - } - if (configuredBackendId != m_initialGraphicsBackend) { - ImGui::TextDisabled("Restart Required"); - } - auto curFileType = (CARDFileType)getSettings().backend.cardFileType.getValue(); - - if (ImGui::BeginCombo("Save File Type", card_type_name(curFileType).data())) { - - if (ImGui::Selectable("GCI Folder", curFileType == CARD_GCIFOLDER)) { - getSettings().backend.cardFileType.setValue(CARD_GCIFOLDER); - config::Save(); - } - - if (ImGui::Selectable("Card Image", curFileType == CARD_RAWIMAGE)) { - getSettings().backend.cardFileType.setValue(CARD_RAWIMAGE); - config::Save(); - } - - ImGui::EndCombo(); - } - - ImGui::EndChild(); - } - - ImGui::SetCursorPosY(endCursorY); - ImGui::NewLine(); - - ImGui::Separator(); - - ImGui::PushFont(ImGuiEngine::fontLarge); - if (ImGuiButtonCenter("Back")) { - m_CurMenu = 0; - } - ImGui::PopFont(); -} - -} // namespace dusk diff --git a/src/dusk/imgui/ImGuiPreLaunchWindow.hpp b/src/dusk/imgui/ImGuiPreLaunchWindow.hpp deleted file mode 100644 index 6cb078a228..0000000000 --- a/src/dusk/imgui/ImGuiPreLaunchWindow.hpp +++ /dev/null @@ -1,23 +0,0 @@ -#pragma once - -namespace dusk { -class ImGuiPreLaunchWindow { -private: - int m_CurMenu = 0; - bool m_IsFirstDraw = true; - std::string m_initialGraphicsBackend; - - bool isSelectedPathValid() const; - -public: - ImGuiPreLaunchWindow(); - void draw(); - - void drawMainMenu(); - void drawOptions(); - - std::string m_selectedIsoPath; - std::string m_errorString; - bool m_isPal = false; -}; -} // namespace dusk diff --git a/src/dusk/main.cpp b/src/dusk/main.cpp index 22cd5a9fc6..e1b2fd0b6e 100644 --- a/src/dusk/main.cpp +++ b/src/dusk/main.cpp @@ -5,17 +5,110 @@ #endif #include +#include "dusk/main.h" +#include +#include +#include #include #include +#include +#include #include #include #include +#if !defined(_WIN32) +#include +#if defined(__APPLE__) +#include +#endif +#endif + int game_main(int argc, char* argv[]); namespace { +bool RestartProcess(int argc, char* argv[]) { +#if defined(__ANDROID__) || (defined(TARGET_OS_IOS) && TARGET_OS_IOS) || \ + (defined(TARGET_OS_TV) && TARGET_OS_TV) + (void)argc; + (void)argv; + return false; +#elif _WIN32 + std::wstring commandLine = GetCommandLineW(); + STARTUPINFOW startupInfo{}; + startupInfo.cb = sizeof(startupInfo); + PROCESS_INFORMATION processInfo{}; + if (!CreateProcessW(nullptr, commandLine.data(), nullptr, nullptr, FALSE, 0, nullptr, nullptr, + &startupInfo, &processInfo)) + { + fprintf(stderr, "Failed to restart Dusk: CreateProcessW error %lu\n", GetLastError()); + return false; + } + + CloseHandle(processInfo.hThread); + CloseHandle(processInfo.hProcess); + return true; +#else + std::filesystem::path executablePath; + +#if defined(__APPLE__) + uint32_t pathSize = 0; + _NSGetExecutablePath(nullptr, &pathSize); + if (pathSize > 0) { + std::string path(pathSize, '\0'); + if (_NSGetExecutablePath(path.data(), &pathSize) == 0) { + path.resize(std::strlen(path.c_str())); + std::error_code ec; + executablePath = std::filesystem::weakly_canonical(path, ec); + if (ec) { + executablePath = path; + } + } + } +#elif defined(__linux__) + std::array path{}; + const ssize_t len = readlink("/proc/self/exe", path.data(), path.size() - 1); + if (len > 0) { + path[static_cast(len)] = '\0'; + executablePath = path.data(); + } +#endif + + if (executablePath.empty() && argc > 0 && argv[0] != nullptr && argv[0][0] != '\0') { + std::error_code ec; + executablePath = std::filesystem::absolute(argv[0], ec); + if (ec) { + executablePath = argv[0]; + } + } + + if (executablePath.empty()) { + fprintf(stderr, "Failed to restart Dusk: unable to resolve executable path\n"); + return false; + } + + std::vector args; + args.reserve(static_cast(std::max(argc, 1))); + args.push_back(executablePath.string()); + for (int i = 1; i < argc; ++i) { + args.emplace_back(argv[i] != nullptr ? argv[i] : ""); + } + + std::vector execArgv; + execArgv.reserve(args.size() + 1); + for (auto& arg : args) { + execArgv.push_back(arg.data()); + } + execArgv.push_back(nullptr); + + execv(executablePath.c_str(), execArgv.data()); + fprintf(stderr, "Failed to restart Dusk: execv failed: %s\n", std::strerror(errno)); + return false; +#endif +} + #if _WIN32 bool ShouldShowWindowsConsole(int argc, char* argv[]) { if (const auto* env = std::getenv("DUSK_CONSOLE")) { @@ -53,19 +146,25 @@ void WindowsSetupConsole(bool showConsole) { SetConsoleOutputCP(CP_UTF8); if (const HANDLE stdoutHandle = GetStdHandle(STD_OUTPUT_HANDLE); - stdoutHandle != INVALID_HANDLE_VALUE && stdoutHandle != nullptr) { + stdoutHandle != INVALID_HANDLE_VALUE && stdoutHandle != nullptr) + { DWORD consoleMode = 0; if (GetConsoleMode(stdoutHandle, &consoleMode)) { SetConsoleMode(stdoutHandle, - consoleMode | ENABLE_PROCESSED_OUTPUT - | ENABLE_VIRTUAL_TERMINAL_PROCESSING); + consoleMode | ENABLE_PROCESSED_OUTPUT | ENABLE_VIRTUAL_TERMINAL_PROCESSING); } } } int DuskMain(int argc, char* argv[]) { WindowsSetupConsole(ShouldShowWindowsConsole(argc, argv)); - return game_main(argc, argv); + const int result = game_main(argc, argv); + if constexpr (dusk::SupportsProcessRestart) { + if (dusk::RestartRequested) { + return RestartProcess(argc, argv) ? 0 : result; + } + } + return result; } std::vector WideArgsToUtf8(int argc, wchar_t** argv) { @@ -81,8 +180,8 @@ std::vector WideArgsToUtf8(int argc, wchar_t** argv) { } std::vector utf8Buffer(static_cast(requiredSize)); - WideCharToMultiByte(CP_UTF8, 0, argv[i], -1, utf8Buffer.data(), requiredSize, nullptr, - nullptr); + WideCharToMultiByte( + CP_UTF8, 0, argv[i], -1, utf8Buffer.data(), requiredSize, nullptr, nullptr); utf8Args.emplace_back(utf8Buffer.data()); } @@ -109,7 +208,11 @@ int RunWindowsGuiEntryPoint() { } #else int DuskMain(int argc, char* argv[]) { - return game_main(argc, argv); + const int result = game_main(argc, argv); + if (dusk::RestartRequested && RestartProcess(argc, argv)) { + return 0; + } + return result; } #endif diff --git a/src/dusk/settings.cpp b/src/dusk/settings.cpp index 9114bfbee3..2b84c8bb47 100644 --- a/src/dusk/settings.cpp +++ b/src/dusk/settings.cpp @@ -27,7 +27,6 @@ UserSettings g_userSettings = { // Quality of Life .enableQuickTransform {"game.enableQuickTransform", false}, .hideTvSettingsScreen {"game.hideTvSettingsScreen", true}, - .skipWarningScreen {"game.skipWarningScreen", true}, .biggerWallets {"game.biggerWallets", false}, .noReturnRupees {"game.noReturnRupees", false}, .disableRupeeCutscenes {"game.disableRupeeCutscenes", false}, @@ -143,7 +142,6 @@ void registerSettings() { Register(g_userSettings.game.language); Register(g_userSettings.game.enableQuickTransform); Register(g_userSettings.game.hideTvSettingsScreen); - Register(g_userSettings.game.skipWarningScreen); Register(g_userSettings.game.biggerWallets); Register(g_userSettings.game.noReturnRupees); Register(g_userSettings.game.disableRupeeCutscenes); diff --git a/src/dusk/ui/achievements.cpp b/src/dusk/ui/achievements.cpp index 93993bb380..67e36eb4f4 100644 --- a/src/dusk/ui/achievements.cpp +++ b/src/dusk/ui/achievements.cpp @@ -65,7 +65,7 @@ public: btn.on_nav_command([this, key = std::string(a.key)](Rml::Event&, NavCommand cmd) { if (cmd == NavCommand::Confirm) { if (mConfirming) { - mDoAud_seStartMenu(Z2SE_SY_CURSOR_OK); + mDoAud_seStartMenu(kSoundClick); AchievementSystem::get().clearOne(key.c_str()); resetConfirm(); } else { @@ -158,7 +158,7 @@ AchievementsWindow::AchievementsWindow() { clearAllBtn.on_nav_command([clearAllPtr, confirmingAll](Rml::Event&, NavCommand cmd) { if (cmd == NavCommand::Confirm) { if (*confirmingAll) { - mDoAud_seStartMenu(Z2SE_SY_CURSOR_OK); + mDoAud_seStartMenu(kSoundClick); AchievementSystem::get().clearAll(); *confirmingAll = false; clearAllPtr->set_text("Clear All Achievements"); diff --git a/src/dusk/ui/bool_button.cpp b/src/dusk/ui/bool_button.cpp index ca4d3f96d8..2ed088542a 100644 --- a/src/dusk/ui/bool_button.cpp +++ b/src/dusk/ui/bool_button.cpp @@ -36,7 +36,7 @@ bool BoolButton::handle_nav_command(NavCommand cmd) { if (cmd == NavCommand::Confirm || cmd == NavCommand::Left || cmd == NavCommand::Right) { const bool newValue = !mGetValue(); mSetValue(newValue); - mDoAud_seStartMenu(newValue ? Z2SE_SY_CURSOR_OK : Z2SE_SY_CURSOR_CANCEL); + mDoAud_seStartMenu(newValue ? kSoundItemEnable : kSoundItemDisable); return true; } return false; diff --git a/src/dusk/ui/button.cpp b/src/dusk/ui/button.cpp index ac27b03d5a..fb11571af4 100644 --- a/src/dusk/ui/button.cpp +++ b/src/dusk/ui/button.cpp @@ -37,7 +37,6 @@ Button& Button::on_pressed(ButtonCallback callback) { // TODO: convert this to a FluentComponent method? on_nav_command([callback = std::move(callback)](Rml::Event&, NavCommand cmd) { if (cmd == NavCommand::Confirm) { - mDoAud_seStartMenu(Z2SE_SY_CURSOR_OK); callback(); return true; } diff --git a/src/dusk/ui/component.cpp b/src/dusk/ui/component.cpp index 466420eb1e..748df848be 100644 --- a/src/dusk/ui/component.cpp +++ b/src/dusk/ui/component.cpp @@ -59,16 +59,6 @@ void Component::set_disabled(bool value) { } } -Rml::Element* Component::append(Rml::Element* parent, const Rml::String& tag) { - if (parent == nullptr) { - return nullptr; - } - auto* doc = parent->GetOwnerDocument(); - if (doc == nullptr) { - return nullptr; - } - return parent->AppendChild(doc->CreateElement(tag)); -} void Component::listen(Rml::Element* element, Rml::EventId event, ScopedEventListener::Callback callback, bool capture) { if (element == nullptr) { diff --git a/src/dusk/ui/component.hpp b/src/dusk/ui/component.hpp index 0c49ce3254..e0a602e7fd 100644 --- a/src/dusk/ui/component.hpp +++ b/src/dusk/ui/component.hpp @@ -47,7 +47,6 @@ public: Rml::Element* root() const { return mRoot; } protected: - static Rml::Element* append(Rml::Element* parent, const Rml::String& tag); void clear_children(); Rml::Element* mRoot = nullptr; diff --git a/src/dusk/ui/controller_config.cpp b/src/dusk/ui/controller_config.cpp index 9a3238725e..038248d7d0 100644 --- a/src/dusk/ui/controller_config.cpp +++ b/src/dusk/ui/controller_config.cpp @@ -322,6 +322,7 @@ void ControllerConfigWindow::render_page(Pane& pane, int port, Page page) { .isSelected = [port] { return PADGetIndexForPort(port) < 0; }, }) .on_pressed([this, port] { + mDoAud_seStartMenu(kSoundItemChange); cancel_pending_binding(); PADClearPort(port); PADSerializeMappings(); @@ -335,6 +336,7 @@ void ControllerConfigWindow::render_page(Pane& pane, int port, Page page) { [port, i] { return PADGetIndexForPort(port) == static_cast(i); }, }) .on_pressed([this, port, i] { + mDoAud_seStartMenu(kSoundItemChange); cancel_pending_binding(); PADSetPortForIndex(i, port); PADSerializeMappings(); diff --git a/src/dusk/ui/document.cpp b/src/dusk/ui/document.cpp index 9d581d21ca..a7bcc3f9ed 100644 --- a/src/dusk/ui/document.cpp +++ b/src/dusk/ui/document.cpp @@ -44,15 +44,8 @@ Document::Document(const Rml::String& source) : mDocument(load_document(source)) if (cmd == NavCommand::None) { return; } - auto* prevFocused = mDocument->GetFocusLeafNode(); if (handle_nav_command(event, cmd)) { event.StopPropagation(); - return; - } - if ((cmd == NavCommand::Up || cmd == NavCommand::Down) && - mDocument->GetFocusLeafNode() != prevFocused) - { - mDoAud_seStartMenu(Z2SE_SY_NAME_CURSOR); } }); } @@ -113,7 +106,7 @@ bool Document::visible() const { bool Document::handle_nav_command(Rml::Event& event, NavCommand cmd) { if (cmd == NavCommand::Menu) { - mDoAud_seStartMenu(visible() ? Z2SE_SY_MENU_OUT : Z2SE_SY_MENU_IN); + mDoAud_seStartMenu(visible() ? kSoundMenuClose : kSoundMenuOpen); toggle(); return true; } diff --git a/src/dusk/ui/editor.cpp b/src/dusk/ui/editor.cpp index 1ba44f0a27..fd42409ed2 100644 --- a/src/dusk/ui/editor.cpp +++ b/src/dusk/ui/editor.cpp @@ -185,7 +185,10 @@ void populate_stage_picker(Pane& pane, std::function getStageFile return getStageFile() == stageFile; }, }) - .on_pressed([setStageFile, stageFile = map.mapFile] { setStageFile(stageFile); }); + .on_pressed([setStageFile, stageFile = map.mapFile] { + mDoAud_seStartMenu(kSoundItemChange); + setStageFile(stageFile); + }); } } } @@ -741,11 +744,13 @@ void populate_toggle_group(Pane& pane, const std::vector& entries) pane.clear(); pane.add_section("Actions"); pane.add_button("Select All").on_pressed([entries] { + mDoAud_seStartMenu(kSoundItemChange); for (const auto& entry : entries) { entry.setSelected(true); } }); pane.add_button("Select None").on_pressed([entries] { + mDoAud_seStartMenu(kSoundItemChange); for (const auto& entry : entries) { entry.setSelected(false); } @@ -758,6 +763,7 @@ void populate_toggle_group(Pane& pane, const std::vector& entries) .isSelected = entry.isSelected, }) .on_pressed([isSelected = entry.isSelected, setSelected = entry.setSelected] { + mDoAud_seStartMenu(kSoundItemChange); setSelected(!isSelected()); }); } @@ -878,7 +884,10 @@ void populate_item_slot_picker(Pane& pane, int slot) { pane.clear(); pane.add_section("Actions"); pane.add_button(fmt::format("Default ({})", get_item_name(get_slot_default(slot)))) - .on_pressed([slot] { dComIfGs_setItem(slot, get_slot_default(slot)); }); + .on_pressed([slot] { + mDoAud_seStartMenu(kSoundItemChange); + dComIfGs_setItem(slot, get_slot_default(slot)); + }); pane.add_section("Items"); pane.add_button( @@ -886,7 +895,10 @@ void populate_item_slot_picker(Pane& pane, int slot) { .text = "None", .isSelected = [slot] { return get_player_item()->mItems[slot] == dItemNo_NONE_e; }, }) - .on_pressed([slot] { dComIfGs_setItem(slot, dItemNo_NONE_e); }); + .on_pressed([slot] { + mDoAud_seStartMenu(kSoundItemChange); + dComIfGs_setItem(slot, dItemNo_NONE_e); + }); for (const auto& [itemId, item] : itemMap) { if (item.m_type != ITEMTYPE_EQUIP_e) { continue; @@ -896,15 +908,24 @@ void populate_item_slot_picker(Pane& pane, int slot) { .text = item.m_name, .isSelected = [slot, itemId] { return get_player_item()->mItems[slot] == itemId; }, }) - .on_pressed([slot, itemId] { dComIfGs_setItem(slot, static_cast(itemId)); }); + .on_pressed([slot, itemId] { + mDoAud_seStartMenu(kSoundItemChange); + dComIfGs_setItem(slot, static_cast(itemId)); + }); } } void populate_item_flag_picker(Pane& pane) { pane.clear(); pane.add_section("Actions"); - pane.add_button("Select All").on_pressed([] { set_all_item_first_bits(true); }); - pane.add_button("Clear None").on_pressed([] { set_all_item_first_bits(false); }); + pane.add_button("Select All").on_pressed([] { + mDoAud_seStartMenu(kSoundItemChange); + set_all_item_first_bits(true); + }); + pane.add_button("Clear None").on_pressed([] { + mDoAud_seStartMenu(kSoundItemChange); + set_all_item_first_bits(false); + }); pane.add_section("Items"); for (const auto& [itemId, item] : itemMap) { @@ -916,7 +937,10 @@ void populate_item_flag_picker(Pane& pane) { .text = item.m_name, .isSelected = [itemId] { return dComIfGs_isItemFirstBit(static_cast(itemId)); }, }) - .on_pressed([itemId] { toggle_item_first_bit(static_cast(itemId)); }); + .on_pressed([itemId] { + mDoAud_seStartMenu(kSoundItemChange); + toggle_item_first_bit(static_cast(itemId)); + }); } } @@ -927,13 +951,19 @@ void populate_select_item_picker(Pane& pane, u8& selectItemData) { .text = "None", .isSelected = [&selectItemData] { return selectItemData == dItemNo_NONE_e; }, }) - .on_pressed([&selectItemData] { selectItemData = dItemNo_NONE_e; }); + .on_pressed([&selectItemData] { + mDoAud_seStartMenu(kSoundItemChange); + selectItemData = dItemNo_NONE_e; + }); for (int i = 0; i < 24; i++) { pane.add_button({ .text = item_label_for_slot(i), .isSelected = [i, &selectItemData] { return selectItemData == i; }, }) - .on_pressed([i, &selectItemData] { selectItemData = i; }); + .on_pressed([i, &selectItemData] { + mDoAud_seStartMenu(kSoundItemChange); + selectItemData = i; + }); } } @@ -946,6 +976,7 @@ void populate_select_clothes_picker(Pane& pane) { .isSelected = [id] { return get_player_status()->mSelectEquip[0] == id; }, }) .on_pressed([id] { + mDoAud_seStartMenu(kSoundItemChange); dMeter2Info_setCloth(id, false); daPy_getPlayerActorClass()->setClothesChange(0); }); @@ -964,7 +995,10 @@ void populate_select_equip_picker(Pane& pane, u8& equip, const std::arraygetWalletSize() == i; }, }) - .on_pressed([i] { get_player_status()->setWalletSize(i); }); + .on_pressed([i] { + mDoAud_seStartMenu(kSoundItemChange); + get_player_status()->setWalletSize(i); + }); } } @@ -1002,7 +1039,10 @@ void populate_form_picker(Pane& pane) { .text = formNames[i], .isSelected = [i] { return get_player_status()->getTransformStatus() == i; }, }) - .on_pressed([i] { get_player_status()->setTransformStatus(i); }); + .on_pressed([i] { + mDoAud_seStartMenu(kSoundItemChange); + get_player_status()->setTransformStatus(i); + }); } } @@ -1013,7 +1053,10 @@ void add_toggle_button(Pane& pane, ToggleEntry entry) { .text = entry.text, .isSelected = isSelected, }) - .on_pressed([isSelected, setSelected] { setSelected(!isSelected()); }); + .on_pressed([isSelected, setSelected] { + mDoAud_seStartMenu(kSoundItemChange); + setSelected(!isSelected()); + }); } template @@ -1126,8 +1169,14 @@ void populate_collect_clothes_picker(Pane& pane) { void populate_poe_souls_picker(Pane& pane) { pane.clear(); pane.add_section("Actions"); - pane.add_button("All 60").on_pressed([] { dComIfGs_setPohSpiritNum(60); }); - pane.add_button("Clear").on_pressed([] { dComIfGs_setPohSpiritNum(0); }); + pane.add_button("All 60").on_pressed([] { + mDoAud_seStartMenu(kSoundItemChange); + dComIfGs_setPohSpiritNum(60); + }); + pane.add_button("Clear").on_pressed([] { + mDoAud_seStartMenu(kSoundItemChange); + dComIfGs_setPohSpiritNum(0); + }); pane.add_section("Value"); pane.add_child(NumberButton::Props{ @@ -1143,10 +1192,12 @@ void populate_max_life_picker(Pane& pane) { pane.clear(); pane.add_section("Actions"); pane.add_button("3 Hearts").on_pressed([] { + mDoAud_seStartMenu(kSoundItemChange); dComIfGs_setMaxLife(15); dComIfGs_setLife(12); }); pane.add_button("20 Hearts").on_pressed([] { + mDoAud_seStartMenu(kSoundItemChange); dComIfGs_setMaxLife(100); dComIfGs_setLife(80); }); @@ -1257,7 +1308,10 @@ void populate_target_type_picker(Pane& pane) { .text = targetTypeNames[type], .isSelected = [type] { return get_player_config()->getAttentionType() == type; }, }) - .on_pressed([type] { get_player_config()->setAttentionType(type); }); + .on_pressed([type] { + mDoAud_seStartMenu(kSoundItemChange); + get_player_config()->setAttentionType(type); + }); } } @@ -1269,7 +1323,10 @@ void populate_sound_mode_picker(Pane& pane) { .text = soundModeNames[mode], .isSelected = [mode] { return get_player_config()->getSound() == mode; }, }) - .on_pressed([mode] { get_player_config()->setSound(mode); }); + .on_pressed([mode] { + mDoAud_seStartMenu(kSoundItemChange); + get_player_config()->setSound(mode); + }); } } @@ -1594,6 +1651,7 @@ EditorWindow::EditorWindow() { leftPane.add_section("Item Wheel"); leftPane.register_control(leftPane.add_button("Default All").on_pressed([&rightPane] { + mDoAud_seStartMenu(kSoundItemChange); for (int slot = 0; slot < 24; ++slot) { dComIfGs_setItem(slot, get_slot_default(slot)); } @@ -1601,6 +1659,7 @@ EditorWindow::EditorWindow() { }), rightPane, {}); leftPane.register_control(leftPane.add_button("Clear All").on_pressed([&rightPane] { + mDoAud_seStartMenu(kSoundItemChange); for (int slot = 0; slot < 24; ++slot) { dComIfGs_setItem(slot, dItemNo_NONE_e); } diff --git a/src/dusk/ui/graphics_tuner.cpp b/src/dusk/ui/graphics_tuner.cpp new file mode 100644 index 0000000000..390d4f9971 --- /dev/null +++ b/src/dusk/ui/graphics_tuner.cpp @@ -0,0 +1,282 @@ +#include "graphics_tuner.hpp" + +#include "Z2AudioLib/Z2SeMgr.h" +#include "m_Do/m_Do_audio.h" + +#include +#include +#include + +#include "dusk/config.hpp" +#include "dusk/settings.h" + +#include +#include + +namespace dusk::ui { +namespace { + +const Rml::String kDocumentSource = R"RML( + + + + + +
+
+
+
+ +
+
+
+ +
+
+ +
+)RML"; + +int get_value(GraphicsOption option) { + switch (option) { + case GraphicsOption::InternalResolution: + return getSettings().game.internalResolutionScale.getValue(); + case GraphicsOption::ShadowResolution: + return getSettings().game.shadowResolutionMultiplier.getValue(); + case GraphicsOption::BloomMode: + return static_cast(getSettings().game.bloomMode.getValue()); + case GraphicsOption::BloomMultiplier: + return std::clamp( + static_cast(getSettings().game.bloomMultiplier.getValue() * 100.0f + 0.5f), 0, + 100); + } + return 0; +} + +void set_value(GraphicsOption option, int value) { + switch (option) { + case GraphicsOption::InternalResolution: + getSettings().game.internalResolutionScale.setValue(value); + VISetFrameBufferScale(static_cast(value)); + break; + case GraphicsOption::ShadowResolution: + getSettings().game.shadowResolutionMultiplier.setValue(value); + break; + case GraphicsOption::BloomMode: + getSettings().game.bloomMode.setValue(static_cast(std::clamp( + value, static_cast(BloomMode::Off), static_cast(BloomMode::Dusk)))); + break; + case GraphicsOption::BloomMultiplier: + getSettings().game.bloomMultiplier.setValue(std::clamp(value, 0, 100) / 100.0f); + break; + } + config::Save(); +} + +Rml::Element* create_stepped_carousel_root(Rml::Element* parent) { + auto* doc = parent->GetOwnerDocument(); + auto root = doc->CreateElement("div"); + root->SetClass("stepped-carousel", true); + root->SetAttribute("tabindex", "0"); + return parent->AppendChild(std::move(root)); +} + +Rml::Element* create_stepped_carousel_arrow( + Rml::Element* parent, const Rml::String& className, const Rml::String& label) { + auto* doc = parent->GetOwnerDocument(); + auto button = doc->CreateElement("button"); + button->SetClass("stepped-carousel-arrow", true); + button->SetClass(className, true); + button->SetInnerRML(label); + return parent->AppendChild(std::move(button)); +} + +} // namespace + +SteppedCarousel::SteppedCarousel(Rml::Element* parent, Props props) + : Component(create_stepped_carousel_root(parent)), mProps(std::move(props)) { + Rml::Element* prevElem = create_stepped_carousel_arrow(mRoot, "prev", ""); + mValueElem = append(mRoot, "div"); + mValueElem->SetClass("stepped-carousel-value", true); + Rml::Element* nextElem = create_stepped_carousel_arrow(mRoot, "next", ""); + + listen(prevElem, Rml::EventId::Click, + [this](Rml::Event&) { handle_nav_command(NavCommand::Left); }); + listen(nextElem, Rml::EventId::Click, + [this](Rml::Event&) { handle_nav_command(NavCommand::Right); }); + listen(mRoot, Rml::EventId::Keydown, [this](Rml::Event& event) { + const auto cmd = map_nav_event(event); + if (cmd != NavCommand::None && handle_nav_command(cmd)) { + event.StopPropagation(); + } + }); +} + +bool SteppedCarousel::focus() { + return Component::focus(); +} + +void SteppedCarousel::update() { + if (mValueElem == nullptr) { + return; + } + const int value = std::clamp(mProps.getValue ? mProps.getValue() : 0, mProps.min, mProps.max); + if (mProps.formatValue) { + mValueElem->SetInnerRML(mProps.formatValue(value)); + } else { + mValueElem->SetInnerRML(std::to_string(value)); + } +} + +bool SteppedCarousel::handle_nav_command(NavCommand cmd) { + if (cmd == NavCommand::Left) { + const int value = mProps.getValue ? mProps.getValue() : 0; + apply(std::clamp(value - mProps.step, mProps.min, mProps.max)); + return true; + } + if (cmd == NavCommand::Right) { + const int value = mProps.getValue ? mProps.getValue() : 0; + apply(std::clamp(value + mProps.step, mProps.min, mProps.max)); + return true; + } + return false; +} + +void SteppedCarousel::apply(int value) { + const int nextValue = std::clamp(value, mProps.min, mProps.max); + const int currentValue = + std::clamp(mProps.getValue ? mProps.getValue() : 0, mProps.min, mProps.max); + if (nextValue == currentValue) { + return; + } + mDoAud_seStartMenu(kSoundItemChange); + if (mProps.onChange) { + mProps.onChange(nextValue); + } +} + +Rml::String format_graphics_setting_value(GraphicsOption option, int value) { + switch (option) { + case GraphicsOption::InternalResolution: + if (value <= 0) { + return "Auto"; + } else { + u32 width = 0; + u32 height = 0; + AuroraGetRenderSize(&width, &height); + return fmt::format("{}× ({}×{})", value, width, height); + } + case GraphicsOption::ShadowResolution: + return fmt::format("{}×", value); + case GraphicsOption::BloomMode: + switch (static_cast(value)) { + case BloomMode::Off: + return "Off"; + case BloomMode::Classic: + return "Classic"; + case BloomMode::Dusk: + return "Dusk"; + } + break; + case GraphicsOption::BloomMultiplier: + return fmt::format("{}%", value); + } + return ""; +} + +GraphicsTuner::GraphicsTuner(GraphicsTunerProps props) + : Document(kDocumentSource), mOption(props.option), mValueMin(props.valueMin), + mValueMax(props.valueMax), mDefaultValue(props.defaultValue) { + if (mDocument == nullptr) { + return; + } + + if (auto* title = mDocument->GetElementById("title")) { + title->SetInnerRML(escape(props.title)); + } + if (auto* description = mDocument->GetElementById("description")) { + description->SetInnerRML(escape(props.helpText)); + } + if (auto* carouselParent = mDocument->GetElementById("carousel-container")) { + add_component(carouselParent, + SteppedCarousel::Props{ + .min = mValueMin, + .max = mValueMax, + .step = 1, + .getValue = [this] { return get_value(mOption); }, + .onChange = [this](int value) { set_value(mOption, value); }, + .formatValue = + [this](int value) { return format_graphics_setting_value(mOption, value); }, + }); + } + + if (auto* footer = mDocument->GetElementById("footer")) { + auto& returnButton = add_component