diff --git a/README.md b/README.md index 85c899aeb0..02b1ded875 100644 --- a/README.md +++ b/README.md @@ -1,37 +1,48 @@ -![DuskLogo](res/logo-mascot.png) +
+ Logo -- ### **[Official Website](https://twilitrealm.dev)** -- ### **[Discord](https://discord.gg/QACynxeyna)** +

+ Official Website + • + Discord +

+
# Overview + Dusk is a reverse-engineered reimplementation of Twilight Princess. + It aims to be as accurate as possible to the original while also providing new options, enhancements, and tools to customize your experience. # Setup -**⚠️ Dusk does NOT provide any copyrighted assets. You must provide your own copy of the game.** -### 1. Verify your ROM dump -First make sure your dump of the game is clean and supported by Dusk. You can do this by checking the sha1 hash of your dump against this list of supported versions. +> [!IMPORTANT] +> Dusk does *not* provide any copyrighted assets. You must provide your own copy of the original game. -| Version | sha1 hash | -|--------------| ---------------------------------------- | -| GameCube USA | 75edd3ddff41f125d1b4ce1a40378f1b565519e7 | -| GameCube PAL | 2601822a488eeb86fb89db16ca8f29c2c953e1ca | +### 1. Verify your dump + +First, make sure your dump of the game is clean and supported by Dusk. You can do this by checking the SHA-1 hash of your dump against this list of supported versions: + +| Version | SHA-1 hash | +|--------------| ------------------------------------------ | +| GameCube USA | `75edd3ddff41f125d1b4ce1a40378f1b565519e7` | +| GameCube EUR | `2601822a488eeb86fb89db16ca8f29c2c953e1ca` | ### 2. Download [Dusk](https://github.com/TwilitRealm/dusk/releases) ### 3. Setup the game -- Extract the zip folder -- Launch Dusk -- Select Options, then set the ISO Path to your supported game dump -- Press Start Game to play! -![Dusk options](assets/dusk_options.png) +- Extract the .zip file +- Launch Dusk +- Press **Select Disc Image**, navigate to your game dump, and select the file +- Press **Start Game** to play! # Building + If you'd like to build Dusk from source, please read the [build instructions](docs/building.md). -Pull Requests are welcomed! Note that we do not accept contributions that are primarily AI generated and will close your PR if we suspect as much. +Pull requests are welcomed! Note that we do not accept contributions that are primarily AI-generated and will close your PR if we suspect as much. # Credits + Special thanks to the [TP decompilation](https://github.com/zeldaret/tp) team, the GC/Wii decompilation community, the [Aurora](https://github.com/encounter/aurora) developers, the [TP speedrunning community](https://zsrtp.link), and all [contributors](https://github.com/TwilitRealm/dusk/graphs/contributors). diff --git a/assets/dusk_options.png b/assets/dusk_options.png deleted file mode 100644 index 4ed4ad7563..0000000000 Binary files a/assets/dusk_options.png and /dev/null differ diff --git a/extern/aurora b/extern/aurora index 41d5c9c5a2..2541890ebf 160000 --- a/extern/aurora +++ b/extern/aurora @@ -1 +1 @@ -Subproject commit 41d5c9c5a2462f57105356ae578d9246e37f6ef2 +Subproject commit 2541890ebf0a44b78e12c0fed384a24fe6be1221 diff --git a/files.cmake b/files.cmake index 64cb21d225..f06884fb35 100644 --- a/files.cmake +++ b/files.cmake @@ -1449,8 +1449,6 @@ set(DUSK_FILES src/dusk/imgui/ImGuiMenuTools.hpp src/dusk/imgui/ImGuiPreLaunchWindow.cpp src/dusk/imgui/ImGuiPreLaunchWindow.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 @@ -1461,16 +1459,20 @@ set(DUSK_FILES src/dusk/imgui/ImGuiSaveEditor.cpp src/dusk/imgui/ImGuiStateShare.hpp src/dusk/imgui/ImGuiStateShare.cpp - src/dusk/imgui/ImGuiAchievements.hpp - src/dusk/imgui/ImGuiAchievements.cpp src/dusk/ui/bool_button.cpp src/dusk/ui/bool_button.hpp src/dusk/ui/button.cpp src/dusk/ui/button.hpp src/dusk/ui/component.cpp src/dusk/ui/component.hpp + src/dusk/ui/controller_config.cpp + 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 diff --git a/include/d/actor/d_a_obj_klift00.h b/include/d/actor/d_a_obj_klift00.h index 865fab8c47..ab5403d51b 100644 --- a/include/d/actor/d_a_obj_klift00.h +++ b/include/d/actor/d_a_obj_klift00.h @@ -25,6 +25,10 @@ public: int Draw(); int Delete(); +#if TARGET_PC + void onInterpCallback(); +#endif + enum Param_e { LOCK_e = (1 << 6), NO_BASE_DISP = (1 << 7) }; @@ -50,6 +54,13 @@ private: /* 0x1020 */ dCcD_Cyl mCylinderCollider; /* 0x115C */ s32 mStopSwingingFrames; +#if TARGET_PC + cXyz mChainInterpPrev[64]; + cXyz mChainInterpCurr[64]; + bool mChainInterpPrevValid; + bool mChainInterpCurrValid; +#endif + // Number of chain models u32 getArg0() { return fopAcM_GetParamBit(this, 0, 6); diff --git a/include/dusk/settings.h b/include/dusk/settings.h index a42d1e8b7f..7f7aa8cd69 100644 --- a/include/dusk/settings.h +++ b/include/dusk/settings.h @@ -56,6 +56,7 @@ struct UserSettings { ConfigVar fanfareVolume; ConfigVar enableReverb; ConfigVar enableHrtf; + ConfigVar menuSounds; } audio; // Game settings @@ -155,7 +156,6 @@ struct UserSettings { ConfigVar showPipelineCompilation; ConfigVar wasPresetChosen; ConfigVar enableCrashReporting; - ConfigVar duskMenuOpen; ConfigVar cardFileType; } backend; }; diff --git a/include/m_Do/m_Do_audio.h b/include/m_Do/m_Do_audio.h index 1d58e8d8f2..4bc2f20c94 100644 --- a/include/m_Do/m_Do_audio.h +++ b/include/m_Do/m_Do_audio.h @@ -5,6 +5,7 @@ #include "Z2AudioLib/Z2EnvSeMgr.h" #include "Z2AudioLib/Z2LinkMgr.h" #include "dusk/audio.h" +#include "dusk/settings.h" class mDoAud_zelAudio_c : public Z2AudioMgr { public: @@ -132,6 +133,18 @@ inline void mDoAud_seStart(u32 i_sfxID, const Vec* i_sePos, u32 param_2, s8 i_re -1.0f, -1.0f, 0); } +#if TARGET_PC +inline void mDoAud_seStartMenu(u32 i_sfxID) { + if (!mDoAud_zelAudio_c::isInitFlag()) { + return; + } + if (!dusk::getSettings().audio.menuSounds.getValue()) { + return; + } + mDoAud_seStart(i_sfxID, nullptr, 0, 0); +} +#endif + inline void mDoAud_seStartLevel(u32 i_sfxID, const Vec* i_sePos, u32 param_2, s8 i_reverb) { DUSK_AUDIO_SKIP() Z2AudioMgr::getInterface()->seStartLevel(i_sfxID, i_sePos, param_2, i_reverb, 1.0f, 1.0f, diff --git a/res/FiraSans-Bold.ttf b/res/FiraSans-Bold.ttf new file mode 100644 index 0000000000..e3593fb0f3 Binary files /dev/null and b/res/FiraSans-Bold.ttf differ diff --git a/res/MaterialSymbolsRounded-Regular.ttf b/res/MaterialSymbolsRounded-Regular.ttf new file mode 100644 index 0000000000..782fc0a406 Binary files /dev/null and b/res/MaterialSymbolsRounded-Regular.ttf differ diff --git a/res/rml/overlay.rcss b/res/rml/overlay.rcss index a8323bb7b2..e18bb436e5 100644 --- a/res/rml/overlay.rcss +++ b/res/rml/overlay.rcss @@ -25,7 +25,6 @@ body { justify-content: flex-end; align-items: stretch; decorator: vertical-gradient(#00000000 #151610F2); - padding: 48dp 0 40dp 0; filter: opacity(0); transition: filter 0.2s linear-in-out; } @@ -42,18 +41,17 @@ body { display: flex; flex-direction: column; gap: 24dp; - padding: 0 32dp; + padding: 48dp 64dp; } @media (max-height: 800dp) { .overlay-root { min-height: 38%; - padding: 32dp 0 28dp 0; } .overlay { gap: 16dp; - padding: 0 24dp; + padding: 32dp 48dp; } } @@ -144,4 +142,6 @@ footer-button.reset { background-color: transparent; opacity: 1; cursor: pointer; + font-family: "Material Symbols Rounded"; + font-weight: normal; } diff --git a/res/rml/prelaunch.rcss b/res/rml/prelaunch.rcss index 6c52f4cf82..9556e74ef2 100644 --- a/res/rml/prelaunch.rcss +++ b/res/rml/prelaunch.rcss @@ -12,7 +12,8 @@ body { background-color: #000000; decorator: image(../prelaunch-bg.png cover left center); filter: opacity(0); - transition: filter 1s 0.1s linear-in-out; + transition: filter 1s 0.2s linear-in-out; + z-index: -1; } body[open] { @@ -104,13 +105,14 @@ hero img { decorator: horizontal-gradient(#FEE685FF #FEE68500); } -disk-status { +disc-info { position: absolute; left: 96dp; bottom: 72dp; display: flex; flex-direction: column; - gap: 8dp; + gap: 12dp; + font-size: 24dp; } version-info { @@ -119,30 +121,53 @@ version-info { bottom: 72dp; display: flex; flex-direction: column; - gap: 8dp; + gap: 12dp; text-align: right; -} - -.status, -.version { font-size: 24dp; } -.status, -.update { +#disc-status { + display: flex; + align-items: center; + gap: 8dp; +} + +#disc-status[status=good] { color: #D8F999; } -.status[bad] { +#disc-status[status=bad] { color: #FFC9C9; } +#disc-status icon { + display: block; + width: 24dp; + height: 24dp; + font-family: "Material Symbols Rounded"; + font-weight: normal; + font-size: 24dp; +} + +#disc-status[status=good] icon { + decorator: text("" center center); +} + +#disc-status[status=bad] icon { + decorator: text("" center center); +} + +#disc-version { + font-size: 20dp; +} + /* TODO: Hidden until an actual update checker is introduced */ .update { display: none; font-size: 16dp; font-weight: bold; cursor: pointer; + color: #D8F999; } .detail, diff --git a/res/rml/tabbing.rcss b/res/rml/tabbing.rcss index e223a4cfad..194ca89f05 100644 --- a/res/rml/tabbing.rcss +++ b/res/rml/tabbing.rcss @@ -1,10 +1,21 @@ tab-bar { display: flex; + position: relative; min-width: 0; overflow: auto hidden; + clip: always; text-transform: uppercase; } +tab-bar scrollbarhorizontal, +tab-bar scrollbarhorizontal sliderarrowdec, +tab-bar scrollbarhorizontal sliderarrowinc, +tab-bar scrollbarhorizontal slidertrack, +tab-bar scrollbarhorizontal sliderbar { + width: 0; + height: 0; +} + tab-bar tab { flex: 0 0 auto; padding: 0 24dp; @@ -31,3 +42,40 @@ tab-bar tab:hover { tab-bar tab:active { decorator: vertical-gradient(#c2a42d10 #c2a42d40); } + +tab-bar[closable] tab-end-spacer { + display: block; + flex: 0 0 64dp; + width: 64dp; + pointer-events: none; +} + +tab-bar[closable] close { + display: block; + position: fixed; + top: 8dp; + right: 8dp; + z-index: 1; + width: 48dp; + height: 48dp; + font-family: "Material Symbols Rounded"; + font-weight: normal; + font-size: 24dp; + color: rgba(224, 219, 200, 70%); + backdrop-filter: blur(2dp); + border-radius: 6dp; + decorator: text("" center center); + transition: color background-color 0.12s linear-in-out; + cursor: pointer; +} + +tab-bar[closable] close:hover, +tab-bar[closable] close:focus-visible { + color: #fff; + background-color: rgba(194, 164, 45, 24%); +} + +tab-bar[closable] close:active { + color: #fff; + background-color: rgba(194, 164, 45, 40%); +} diff --git a/res/rml/window.rcss b/res/rml/window.rcss index 197711f2a0..db3558778a 100644 --- a/res/rml/window.rcss +++ b/res/rml/window.rcss @@ -3,6 +3,7 @@ } body { + display: flex; width: 100%; height: 100%; padding: 64dp; @@ -17,6 +18,7 @@ window { display: flex; flex-flow: column; height: 100%; + width: 100%; max-width: 1088dp; max-height: 768dp; margin: auto; @@ -32,6 +34,15 @@ window { transition: filter transform 0.2s cubic-in-out; } +window.small { + height: auto; + width: auto; +} + +window.preset { + min-width: 650dp; +} + window[open] { filter: opacity(1); transform: scale(1); @@ -62,7 +73,7 @@ window tab-bar tab { window content { display: flex; - flex: 1 1 0; + flex: 1 1 auto; min-width: 0; min-height: 0; overflow: hidden; @@ -72,7 +83,6 @@ window content pane { display: flex; flex-flow: column; flex: 1 1 0; - height: 100%; min-width: 0; min-height: 0; padding: 24dp; @@ -225,15 +235,167 @@ select-button key { font-weight: bold; font-size: 18dp; text-transform: uppercase; - flex: 1 0 auto; + flex: 0 1 auto; } select-button value { - margin-left: auto; + flex: 1 1 auto; + text-align: right; font-size: 20dp; } +select-button value.modified { + font-weight: bold; +} + select-button input { text-align: right; font-size: 20dp; } + +icon { + font-family: "Material Symbols Rounded"; + font-weight: normal; + display: inline-block; + vertical-align: middle; +} + +icon.warning { + width: 1em; + height: 1em; + decorator: text("" center center); + color: #ffcc00; +} + +.achievement-row { + display: flex; + align-items: flex-start; + gap: 10dp; + padding: 12dp 0; + border-bottom: 1dp rgba(146, 135, 91, 30%); +} + +.achievement-info { + display: block; + flex: 1 1 0; + min-width: 0; +} + +.achievement-header { + display: flex; + align-items: center; +} + +.achievement-name { + flex: 1; + font-weight: bold; +} + +.achievement-name.unlocked { + color: #ffa826; +} + +.achievement-badge { + font-size: 14dp; + opacity: 0.7; +} + +.achievement-badge.unlocked { + color: #44cc55; + opacity: 1; +} + +.achievement-badge.locked { + color: #cc4444; + opacity: 1; +} + +.achievement-desc { + display: block; + color: rgba(224, 219, 200, 55%); + font-size: 16dp; + margin: 4dp 0 0 0; +} + +.achievement-progress { + display: block; + font-size: 13dp; + color: rgba(224, 219, 200, 45%); +} + +progressbar { + display: block; + width: 100%; + height: 6dp; + border-radius: 3dp; + background-color: rgba(255, 255, 255, 10%); + margin: 6dp 0 2dp 0; +} + +progressbar.progress-done fill { + background-color: #44aa22; + border-radius: 3dp; +} + +progressbar.progress-ongoing fill { + background-color: #2255bb; + border-radius: 3dp; +} + +button.achievement-clear { + flex: 0 0 auto; + align-self: center; + font-size: 14dp; + padding: 2dp 8dp; + opacity: 0.45; +} + +.preset-dialog { + display: flex; + flex-flow: column; + padding: 32dp; + gap: 20dp; + flex: 0 1 auto; +} + +.preset-title { + display: block; + font-family: "Fira Sans Condensed"; + font-weight: bold; + font-size: 30dp; + text-align: center; +} + +.preset-intro { + display: block; + font-size: 18dp; + text-align: center; + color: rgba(224, 219, 200, 65%); +} + +.preset-grid { + display: flex; + flex-direction: row; + gap: 20dp; + flex: 0 1 auto; + align-items: flex-start; +} + +.preset-col { + display: flex; + flex-flow: column; + gap: 12dp; + flex: 1 1 0; +} + +button.preset-btn { + font-size: 22dp; + padding: 20dp 16dp; +} + +.preset-desc { + display: block; + font-size: 16dp; + color: rgba(224, 219, 200, 65%); + text-align: center; +} diff --git a/src/Z2AudioLib/Z2Audience.cpp b/src/Z2AudioLib/Z2Audience.cpp index 93b3f701c6..dda1a4b2d0 100644 --- a/src/Z2AudioLib/Z2Audience.cpp +++ b/src/Z2AudioLib/Z2Audience.cpp @@ -2,6 +2,7 @@ #include "Z2AudioLib/Z2SoundInfo.h" #if TARGET_PC #include "dusk/audio/DuskDsp.hpp" +#include "dusk/settings.h" #include #endif #include "Z2AudioLib/Z2Calc.h" @@ -705,6 +706,11 @@ f32 Z2Audience::calcRelPosVolume(const Vec& param_0, f32 param_1, int camID) { f32 Z2Audience::calcRelPosPan(const Vec& param_0, int camID) { Vec local_54 = param_0; local_54.y = 0.0f; +#if TARGET_PC + if (dusk::getSettings().game.enableMirrorMode) { + local_54.x = -local_54.x; + } +#endif f32 dVar6 = VECMag(&local_54); if (dVar6 < 0.1f) { diff --git a/src/d/actor/d_a_e_oct_bg.cpp b/src/d/actor/d_a_e_oct_bg.cpp index 14a421a3b6..94fa69596b 100644 --- a/src/d/actor/d_a_e_oct_bg.cpp +++ b/src/d/actor/d_a_e_oct_bg.cpp @@ -517,6 +517,12 @@ void daE_OctBg_c::core_fish_attack() { field_0xbaf = cM_rndFX(80.0f) + 100.0f; } } + #if AVOID_UB + else { + in_f31 = cM_rndF(400.0f) + 80.0f; + field_0xbaf = cM_rndFX(80.0f) + 100.0f; + } + #endif } else if (current.pos.abs(cStack_5c) < 400.0f) { in_f31 = cM_rndF(50.0f) + 20.0f; field_0xbaf = cM_rndFX(20.0f) + 40.0f; diff --git a/src/d/actor/d_a_npc_toby.cpp b/src/d/actor/d_a_npc_toby.cpp index 543d4b9abf..d175d199b7 100644 --- a/src/d/actor/d_a_npc_toby.cpp +++ b/src/d/actor/d_a_npc_toby.cpp @@ -14,6 +14,7 @@ #include "d/actor/d_a_obj_automata.h" #include "d/d_msg_object.h" #include "d/actor/d_a_obj_scannon.h" +#include "dusk/frame_interpolation.h" #include const daNpc_Toby_HIOParam daNpc_Toby_Param_c::m = { @@ -1398,6 +1399,7 @@ int daNpc_Toby_c::cutRepairSCannon(int arg0) { old.pos = current.pos; setAngle(cM_deg2s(5.0f * f32(mPath.getArg0()))); mEventTimer = mPath.getArg2(); + dusk::frame_interp::request_presentation_sync(); } } else if (!mHide) { mHide = 1; diff --git a/src/d/actor/d_a_obj_klift00.cpp b/src/d/actor/d_a_obj_klift00.cpp index 99b204592e..e44cce7467 100644 --- a/src/d/actor/d_a_obj_klift00.cpp +++ b/src/d/actor/d_a_obj_klift00.cpp @@ -11,6 +11,8 @@ #include "d/d_bg_w.h" #include "d/d_cc_uty.h" #include "d/d_com_inf_game.h" +#include "dusk/frame_interpolation.h" +#include "dusk/settings.h" struct daObjKLift00_HIO_c : public mDoHIO_entry_c { daObjKLift00_HIO_c(); @@ -295,6 +297,11 @@ int daObjKLift00_c::Create() { if(getLock()) mStopSwingingFrames = 5; +#if TARGET_PC + mChainInterpPrevValid = false; + mChainInterpCurrValid = false; +#endif + return 1; } @@ -436,6 +443,34 @@ int daObjKLift00_c::Execute(Mtx** i_mtx) { return 1; } +#if TARGET_PC +static void klift00_interp_callback(bool isSimFrame, void* pUserWork) { + static_cast(pUserWork)->onInterpCallback(); +} + +void daObjKLift00_c::onInterpCallback() { + if (!mChainInterpPrevValid || !mChainInterpCurrValid) { + return; + } + + const f32 alpha = dusk::frame_interp::get_interpolation_step(); + cXyz savedPositions[64]; + + for (int i = 0; i < mNumChains; i++) { + savedPositions[i] = mChainPositions[i].mCurrentPos; + const cXyz& p0 = mChainInterpPrev[i]; + const cXyz& p1 = mChainInterpCurr[i]; + mChainPositions[i].mCurrentPos = p0 + (p1 - p0) * alpha; + } + + setMtx(); + + for (int i = 0; i < mNumChains; i++) { + mChainPositions[i].mCurrentPos = savedPositions[i]; + } +} +#endif + int daObjKLift00_c::Draw() { g_env_light.settingTevStruct(16, ¤t.pos, &tevStr); g_env_light.setLightTevColorType_MAJI(mpLiftPlatform, &tevStr); @@ -457,6 +492,22 @@ int daObjKLift00_c::Draw() { dComIfGd_setList(); +#if TARGET_PC + if (dusk::getSettings().game.enableFrameInterpolation) { + if (mChainInterpCurrValid) { + memcpy(mChainInterpPrev, mChainInterpCurr, mNumChains * sizeof(cXyz)); + mChainInterpPrevValid = true; + } + + for (int i = 0; i < mNumChains; i++) { + mChainInterpCurr[i] = mChainPositions[i].mCurrentPos; + } + + mChainInterpCurrValid = true; + dusk::frame_interp::add_interpolation_callback(&klift00_interp_callback, this); + } +#endif + return 1; } diff --git a/src/dusk/OSReport.cpp b/src/dusk/OSReport.cpp index b89a708d78..a54bcf0c78 100644 --- a/src/dusk/OSReport.cpp +++ b/src/dusk/OSReport.cpp @@ -1,3 +1,5 @@ +#include + #include "aurora/lib/logging.hpp" #include "os_report.h" @@ -21,10 +23,35 @@ static bool checkEnabled() { static std::string FormatToString(const char* msg, va_list list) { int ret = vsnprintf(nullptr, 0, msg, list); - std::string buf(ret, '\0'); - vsnprintf(buf.data(), buf.size(), msg, list); - buf.pop_back(); - return buf; + if (ret <= 0) { + return {}; + } + ++ret; + std::unique_ptr buf(new char[ret]); + vsnprintf(buf.get(), ret, msg, list); + buf[ret - 1] = '\0'; + return {buf.get()}; +} + +void OSReport(const char* fmt, ...) { + if (!checkEnabled()) { + return; + } + va_list args; + va_start(args, fmt); + const auto str = FormatToString(fmt, args); + va_end(args); + + Log.info("{}", str); +} + +void OSPanic(const char* file, int line, const char* fmt, ...) { + va_list args; + va_start(args, fmt); + const auto str = FormatToString(fmt, args); + va_end(args); + + Log.fatal("[{}:{}] {}", file, line, str); } void OSReport_Error(const char* fmt, ...) { diff --git a/src/dusk/imgui/ImGuiAchievements.cpp b/src/dusk/imgui/ImGuiAchievements.cpp deleted file mode 100644 index f03e4176ed..0000000000 --- a/src/dusk/imgui/ImGuiAchievements.cpp +++ /dev/null @@ -1,245 +0,0 @@ -#include "ImGuiAchievements.hpp" -#include "ImGuiConfig.hpp" -#include "dusk/achievements.h" -#include "dusk/settings.h" -#include "fmt/format.h" -#include "imgui.h" - -namespace dusk { - -void ImGuiAchievements::notify(std::string name) { - if (m_notifyTimer <= 0.f) { - m_notifyName = std::move(name); - m_notifyTimer = NOTIFY_DURATION; - } else { - m_notifyQueue.push(std::move(name)); - } -} - -void ImGuiAchievements::showNotification() { - if (!getSettings().game.enableAchievementNotifications.getValue()) { - return; - } - if (m_notifyTimer <= 0.f) { - if (m_notifyQueue.empty()) { - return; - } - m_notifyName = std::move(m_notifyQueue.front()); - m_notifyQueue.pop(); - m_notifyTimer = NOTIFY_DURATION; - } - - m_notifyTimer -= ImGui::GetIO().DeltaTime; - - const float alpha = std::min({ - m_notifyTimer / NOTIFY_FADE_TIME, - (NOTIFY_DURATION - m_notifyTimer) / NOTIFY_FADE_TIME, - 1.0f - }); - - const ImGuiViewport* viewport = ImGui::GetMainViewport(); - const float padding = 12.0f; - ImGui::SetNextWindowPos( - ImVec2(viewport->WorkPos.x + viewport->WorkSize.x - padding, viewport->WorkPos.y + padding), - ImGuiCond_Always, ImVec2(1.0f, 0.0f) - ); - - ImGui::SetNextWindowBgAlpha(alpha * 0.92f); - ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(0.08f, 0.06f, 0.01f, alpha * 0.92f)); - ImGui::PushStyleColor(ImGuiCol_Border, ImVec4(1.0f, 0.8f, 0.1f, alpha)); - ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 1.0f, 1.0f, alpha)); - ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 2.0f); - ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(14.0f, 10.0f)); - - constexpr ImGuiWindowFlags flags = - ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_AlwaysAutoResize | - ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoFocusOnAppearing | - ImGuiWindowFlags_NoNav | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoInputs; - - if (ImGui::Begin("##achievement_notify", nullptr, flags)) { - ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 0.82f, 0.1f, alpha)); - ImGui::TextUnformatted("Achievement Unlocked!"); - ImGui::PopStyleColor(); - ImGui::Spacing(); - ImGui::TextUnformatted(m_notifyName.c_str()); - } - ImGui::End(); - - ImGui::PopStyleVar(2); - ImGui::PopStyleColor(3); -} - -void ImGuiAchievements::draw(bool& open) { - showNotification(); - - if (!open) { - return; - } - - ImGui::SetNextWindowSizeConstraints(ImVec2(800, 200), ImVec2(1280, 900)); - ImGui::SetNextWindowSize(ImVec2(800, 480), ImGuiCond_FirstUseEver); - - if (!ImGui::Begin( - "Achievements", &open, - ImGuiWindowFlags_NoFocusOnAppearing | ImGuiWindowFlags_NoNav) - ) - { - ImGui::End(); - return; - } - - const auto achievements = AchievementSystem::get().getAchievements(); - - int unlocked = 0; - for (const auto& a : achievements) { - if (a.unlocked) { - ++unlocked; - } - } - - ImGui::Text("%d / %d achievements unlocked", unlocked, (int)achievements.size()); - ImGui::SameLine(); - config::ImGuiCheckbox("Notifications", getSettings().game.enableAchievementNotifications); - ImGui::Separator(); - - static const struct { - AchievementCategory cat; - const char* label; - ImVec4 color; - } ACHIEVEMENT_CATEGORIES[] = { - {AchievementCategory::Story, "Story", ImVec4(1.0f, 0.82f, 0.1f, 1.0f)}, - {AchievementCategory::Collection, "Collection", ImVec4(0.3f, 0.85f, 0.4f, 1.0f)}, - {AchievementCategory::Challenge, "Challenge", ImVec4(1.0f, 0.65f, 0.15f, 1.0f)}, - {AchievementCategory::Minigame, "Minigame", ImVec4(0.5f, 0.85f, 1.0f, 1.0f)}, - {AchievementCategory::Misc, "Misc", ImVec4(0.65f, 0.65f, 0.65f, 1.0f)}, - {AchievementCategory::Glitched, "Glitched", ImVec4(0.75f, 0.4f, 1.0f, 1.0f)}, - }; - - const float footerHeight = ImGui::GetStyle().ItemSpacing.y + ImGui::GetFrameHeightWithSpacing(); - - if (ImGui::BeginTabBar("##achievement_tabs", ImGuiTabBarFlags_FittingPolicyScroll)) { - for (const auto& catInfo : ACHIEVEMENT_CATEGORIES) { - int catTotal = 0, catUnlocked = 0; - for (const auto& a : achievements) { - if (a.category == catInfo.cat) { - ++catTotal; - if (a.unlocked) { - ++catUnlocked; - } - } - } - if (catTotal == 0) { - continue; - } - - const std::string tabLabel = fmt::format("{} ({}/{})###{}", catInfo.label, catUnlocked, catTotal, catInfo.label); - - ImGui::PushStyleColor(ImGuiCol_Text, catInfo.color); - const bool tabOpen = ImGui::BeginTabItem(tabLabel.c_str()); - ImGui::PopStyleColor(); - - if (tabOpen) { - ImGui::BeginChild( - "##cat_list", - ImVec2(0, -footerHeight), - ImGuiChildFlags_None, - ImGuiWindowFlags_NoBackground - ); - - ImGui::Spacing(); - - for (const auto& a : achievements) { - if (a.category != catInfo.cat) { - continue; - } - ImGui::PushID(a.key); - ImGui::BeginGroup(); - - ImGui::PushStyleColor( - ImGuiCol_Text, - a.unlocked ? - ImVec4(1.0f, 0.65f, 0.15f, 1.0f) : - ImGui::GetStyleColorVec4(ImGuiCol_Text) - ); - - ImGui::TextUnformatted(a.name); - ImGui::PopStyleColor(); - - const char* statusLabel = a.unlocked ? "[Unlocked]" : "[Locked]"; - ImGui::SameLine( - ImGui::GetContentRegionMax().x - - ImGui::CalcTextSize(statusLabel).x - ); - - if (a.unlocked) { - ImGui::TextColored(ImVec4(0.2f, 0.8f, 0.2f, 1.0f), "%s", statusLabel); - } else { - ImGui::TextColored(ImVec4(0.8f, 0.2f, 0.2f, 1.0f), "%s", statusLabel); - } - - ImGui::TextDisabled("%s", a.description); - - if (a.isCounter) { - const float fraction = a.goal > 0 ? (float)(a.progress) / (float)(a.goal) : 1.0f; - const std::string overlay = fmt::format("{} / {}", a.progress, a.goal); - ImGui::PushStyleColor( - ImGuiCol_PlotHistogram, - a.unlocked ? - ImVec4(0.4f, 0.7f, 0.1f, 1.0f) : - ImVec4(0.2f, 0.45f, 0.8f, 1.0f) - ); - ImGui::ProgressBar(fraction, ImVec2(-1.0f, 0.0f), overlay.c_str()); - ImGui::PopStyleColor(); - } - - ImGui::EndGroup(); - - if (ImGui::IsItemHovered() && ImGui::IsMouseClicked(ImGuiMouseButton_Right)) { - ImGui::OpenPopup("##ctx"); - } - - if (ImGui::BeginPopup("##ctx")) { - ImGui::TextDisabled("%s", a.name); - ImGui::Separator(); - if (ImGui::MenuItem("Clear Achievement")) { - AchievementSystem::get().clearOne(a.key); - } - ImGui::EndPopup(); - } - - ImGui::Spacing(); - ImGui::PopID(); - } - - ImGui::EndChild(); - ImGui::EndTabItem(); - } - } - ImGui::EndTabBar(); - } - - ImGui::Separator(); - ImGui::Spacing(); - - if (ImGui::Button("Clear All Achievements")) { - ImGui::OpenPopup("##confirm_clear"); - } - - if (ImGui::BeginPopup("##confirm_clear")) { - ImGui::Text("Reset all achievement progress?"); - ImGui::Spacing(); - if (ImGui::Button("Yes, reset all")) { - AchievementSystem::get().clearAll(); - ImGui::CloseCurrentPopup(); - } - ImGui::SameLine(); - if (ImGui::Button("Cancel")) { - ImGui::CloseCurrentPopup(); - } - ImGui::EndPopup(); - } - - ImGui::End(); -} - -} // namespace dusk diff --git a/src/dusk/imgui/ImGuiAchievements.hpp b/src/dusk/imgui/ImGuiAchievements.hpp deleted file mode 100644 index 5ee77373fc..0000000000 --- a/src/dusk/imgui/ImGuiAchievements.hpp +++ /dev/null @@ -1,23 +0,0 @@ -#pragma once - -#include -#include - -namespace dusk { - -class ImGuiAchievements { -public: - void draw(bool& open); - void notify(std::string name); - -private: - void showNotification(); - - std::string m_notifyName; - float m_notifyTimer = 0.f; - std::queue m_notifyQueue; - static constexpr float NOTIFY_DURATION = 4.0f; - static constexpr float NOTIFY_FADE_TIME = 0.5f; -}; - -} // namespace dusk diff --git a/src/dusk/imgui/ImGuiConsole.cpp b/src/dusk/imgui/ImGuiConsole.cpp index 321cb2e579..355ef14450 100644 --- a/src/dusk/imgui/ImGuiConsole.cpp +++ b/src/dusk/imgui/ImGuiConsole.cpp @@ -10,10 +10,10 @@ #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_events.h" #include "SDL3/SDL_mouse.h" -#include "aurora/lib/window.hpp" #include "dusk/achievements.h" #include "dusk/audio/DuskAudioSystem.h" #include "dusk/config.hpp" @@ -22,6 +22,8 @@ #include "dusk/livesplit.h" #include "dusk/main.h" #include "dusk/settings.h" +#include "f_pc/f_pc_manager.h" +#include "f_pc/f_pc_name.h" #include "m_Do/m_Do_controller_pad.h" #include "m_Do/m_Do_main.h" #include "tracy/Tracy.hpp" @@ -35,14 +37,6 @@ using namespace std::string_literals; using namespace std::string_view_literals; namespace { -ImVec2 TouchEventToScreenPos(const SDL_TouchFingerEvent& touch) { - const AuroraWindowSize size = aurora::window::get_window_size(); - return ImVec2{ - touch.x * static_cast(size.width), - touch.y * static_cast(size.height), - }; -} - ImGuiWindow* FindDragScrollWindow(ImGuiWindow* window) { while (window != nullptr) { const bool canScrollX = window->ScrollMax.x > 0.0f; @@ -241,48 +235,7 @@ namespace dusk { ImGuiConsole::ImGuiConsole() {} void ImGuiConsole::HandleSDLEvent(const SDL_Event& event) { - if (!IsGameLaunched) { - return; - } - - switch (event.type) { - case SDL_EVENT_FINGER_DOWN: - if (!m_touchTapActive) { - m_touchTapActive = true; - m_touchTapMoved = false; - m_touchTapFingerId = event.tfinger.fingerID; - m_touchTapStartPos = TouchEventToScreenPos(event.tfinger); - } - break; - case SDL_EVENT_FINGER_MOTION: - if (m_touchTapActive && m_touchTapFingerId == event.tfinger.fingerID) { - const auto currentPos = TouchEventToScreenPos(event.tfinger); - const auto delta = currentPos - m_touchTapStartPos; - if (ImLengthSqr(delta) > 144.0f) { - m_touchTapMoved = true; - } - } - break; - case SDL_EVENT_FINGER_UP: - if (m_touchTapActive && m_touchTapFingerId == event.tfinger.fingerID) { - const bool shouldToggle = - !m_touchTapMoved && (m_isHidden || !ImGui::GetIO().WantCaptureMouse); - m_touchTapActive = false; - m_touchTapMoved = false; - if (shouldToggle) { - m_isHidden = !m_isHidden; - getSettings().backend.duskMenuOpen.setValue(!m_isHidden); - Save(); - } - } - break; - case SDL_EVENT_FINGER_CANCELED: - m_touchTapActive = false; - m_touchTapMoved = false; - break; - default: - break; - } + (void)event; } void ImGuiConsole::UpdateSettings() { @@ -310,7 +263,8 @@ namespace dusk { } } - if ((ImGui::IsKeyDown(ImGuiKey_LeftCtrl) || ImGui::IsKeyDown(ImGuiKey_RightCtrl)) && + if (!fpcM_SearchByName(fpcNm_LOGO_SCENE_e) && + (ImGui::IsKeyDown(ImGuiKey_LeftCtrl) || ImGui::IsKeyDown(ImGuiKey_RightCtrl)) && ImGui::IsKeyPressed(ImGuiKey_R)) { JUTGamePad::C3ButtonReset::sResetSwitchPushing = true; @@ -320,23 +274,14 @@ namespace dusk { ImGuiMenuGame::ToggleFullscreen(); } - if (ImGui::IsKeyPressed(ImGuiKey_Escape) && getSettings().video.enableFullscreen) { - ImGuiMenuGame::ToggleFullscreen(); - } - // if (!dusk::IsGameLaunched) { // m_preLaunchWindow.draw(); // } - m_isHidden = !getSettings().backend.duskMenuOpen; if (ImGui::GetIO().KeyShift && ImGui::IsKeyPressed(ImGuiKey_F1)) { m_isHidden = !m_isHidden; } bool showMenu = !m_isHidden; - if (getSettings().backend.duskMenuOpen != showMenu) { - getSettings().backend.duskMenuOpen.setValue(showMenu); - Save(); - } // The menu bar renders with ImGuiCol_WindowBg behind it. We just want ImGuiCol_MenuBarBg, // so make the window bg fully transparent temporarily @@ -359,14 +304,9 @@ namespace dusk { } ImGui::PopStyleColor(); - if (!getSettings().backend.wasPresetChosen) { - m_firstRunPreset.draw(); - return; - } - if (dusk::IsGameLaunched && !m_isLaunchInitialized) { AddToast(ImGui::GetIO().MouseSource == ImGuiMouseSource_TouchScreen ? - "Tap to toggle menu"s : + "3-finger tap to toggle menu"s : "Press F1 to toggle menu"s, 4.f); m_isLaunchInitialized = true; @@ -402,7 +342,7 @@ namespace dusk { m_menuTools.ShowSaveEditor(); m_menuTools.ShowStateShare(); } - m_menuTools.ShowAchievements(); + 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 a6c8b48fc7..1755c02856 100644 --- a/src/dusk/imgui/ImGuiConsole.hpp +++ b/src/dusk/imgui/ImGuiConsole.hpp @@ -6,9 +6,7 @@ #include #include -#include -#include "ImGuiFirstRunPreset.hpp" #include "ImGuiMenuGame.hpp" #include "ImGuiMenuTools.hpp" #include "ImGuiPreLaunchWindow.hpp" @@ -42,15 +40,10 @@ private: bool m_isHidden = true; bool m_isLaunchInitialized = false; - bool m_touchTapActive = false; - bool m_touchTapMoved = false; - SDL_FingerID m_touchTapFingerId = 0; - ImVec2 m_touchTapStartPos = {}; ImGuiWindow* m_dragScrollWindow = nullptr; ImVec2 m_dragScrollLastMousePos = {}; std::deque m_toasts; - ImGuiFirstRunPreset m_firstRunPreset; ImGuiMenuGame m_menuGame; ImGuiPreLaunchWindow m_preLaunchWindow; diff --git a/src/dusk/imgui/ImGuiFirstRunPreset.cpp b/src/dusk/imgui/ImGuiFirstRunPreset.cpp deleted file mode 100644 index 6b561cd877..0000000000 --- a/src/dusk/imgui/ImGuiFirstRunPreset.cpp +++ /dev/null @@ -1,141 +0,0 @@ -#include "ImGuiFirstRunPreset.hpp" - -#include "imgui.h" -#include "ImGuiConsole.hpp" -#include "ImGuiEngine.hpp" -#include "dusk/settings.h" -#include "dusk/config.hpp" -#include - -namespace dusk { - -static void ApplyPresetClassic() { - auto& s = getSettings(); - s.video.lockAspectRatio.setValue(true); - s.game.bloomMode.setValue(BloomMode::Classic); - AuroraSetViewportPolicy(AURORA_VIEWPORT_FIT); -} - -static void ApplyPresetHD() { - auto& s = getSettings(); - s.game.bloomMode.setValue(BloomMode::Classic); - s.game.hideTvSettingsScreen.setValue(true); - s.game.skipWarningScreen.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.biggerWallets.setValue(true); - s.game.invertCameraXAxis.setValue(true); - s.game.freeCamera.setValue(true); - s.game.no2ndFishForCat.setValue(true); -} - -static void ApplyPresetDusk() { - ApplyPresetHD(); - - auto& s = getSettings(); - s.game.enableAchievementNotifications.setValue(true); - s.game.enableQuickTransform.setValue(true); - s.game.instantSaves.setValue(true); - s.game.midnasLamentNonStop.setValue(true); - s.game.enableFrameInterpolation.setValue(true); - s.game.sunsSong.setValue(true); - s.game.bloomMode.setValue(BloomMode::Dusk); - s.game.autoSave.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("Classic##btn", ImVec2(ImGui::GetContentRegionAvail().x, 80.0f * ImGuiScale()))) { - chosen = 0; - } - - ImGui::TableSetColumnIndex(2); - if (ImGui::Button("HD##btn", ImVec2(ImGui::GetContentRegionAvail().x, 80.0f * ImGuiScale()))) { - chosen = 1; - } - - ImGui::TableSetColumnIndex(4); - if (ImGui::Button("Dusk##btn", ImVec2(ImGui::GetContentRegionAvail().x, 80.0f * ImGuiScale()))) - { - chosen = 2; - } - - ImGui::PopFont(); - - ImGui::TableNextRow(); - - ImGui::TableSetColumnIndex(0); - ImGui::Spacing(); - 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 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) ApplyPresetClassic(); - if (chosen == 1) ApplyPresetHD(); - if (chosen == 2) ApplyPresetDusk(); - - getSettings().backend.wasPresetChosen.setValue(true); - 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 deleted file mode 100644 index 33ba6b3d0f..0000000000 --- a/src/dusk/imgui/ImGuiFirstRunPreset.hpp +++ /dev/null @@ -1,14 +0,0 @@ -#pragma once - -namespace dusk { - -class ImGuiFirstRunPreset { -public: - void draw(); - -private: - bool m_opened = false; - bool m_done = false; -}; - -} // namespace dusk diff --git a/src/dusk/imgui/ImGuiMenuTools.cpp b/src/dusk/imgui/ImGuiMenuTools.cpp index da217022f8..714add87c4 100644 --- a/src/dusk/imgui/ImGuiMenuTools.cpp +++ b/src/dusk/imgui/ImGuiMenuTools.cpp @@ -66,7 +66,6 @@ namespace dusk { ImGui::EndDisabled(); } - ImGui::MenuItem("Achievements", nullptr, &m_showAchievements); #if DUSK_CAN_OPEN_DATA_FOLDER ImGui::Separator(); @@ -269,11 +268,65 @@ namespace dusk { ImGui::PopFont(); } - void ImGuiMenuTools::ShowAchievements() { - m_achievementsWindow.draw(m_showAchievements); + 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::notifyAchievement(std::string name) { - m_achievementsWindow.notify(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 de94ad2d8f..7ae0e5bec6 100644 --- a/src/dusk/imgui/ImGuiMenuTools.hpp +++ b/src/dusk/imgui/ImGuiMenuTools.hpp @@ -2,10 +2,10 @@ #define DUSK_IMGUI_MENUTOOLS_HPP #include +#include #include #include "imgui.h" -#include "ImGuiAchievements.hpp" #include "ImGuiSaveEditor.hpp" #include "ImGuiStateShare.hpp" @@ -27,8 +27,8 @@ namespace dusk { void ShowAudioDebug(); void ShowSaveEditor(); void ShowStateShare(); - void ShowAchievements(); void notifyAchievement(std::string name); + void showAchievementNotification(); private: bool m_showDebugOverlay = false; @@ -69,8 +69,11 @@ namespace dusk { bool m_showStateShare = false; ImGuiStateShare m_stateShare; - bool m_showAchievements = false; - ImGuiAchievements m_achievementsWindow; + 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/settings.cpp b/src/dusk/settings.cpp index f09dd95061..9114bfbee3 100644 --- a/src/dusk/settings.cpp +++ b/src/dusk/settings.cpp @@ -18,6 +18,7 @@ UserSettings g_userSettings = { .fanfareVolume {"audio.fanfareVolume", 100}, .enableReverb {"audio.enableReverb", true}, .enableHrtf {"audio.enableHrtf", false}, + .menuSounds {"audio.menuSounds", true}, }, .game = { @@ -25,8 +26,8 @@ UserSettings g_userSettings = { // Quality of Life .enableQuickTransform {"game.enableQuickTransform", false}, - .hideTvSettingsScreen {"game.hideTvSettingsScreen", false}, - .skipWarningScreen {"game.skipWarningScreen", false}, + .hideTvSettingsScreen {"game.hideTvSettingsScreen", true}, + .skipWarningScreen {"game.skipWarningScreen", true}, .biggerWallets {"game.biggerWallets", false}, .noReturnRupees {"game.noReturnRupees", false}, .disableRupeeCutscenes {"game.disableRupeeCutscenes", false}, @@ -47,11 +48,11 @@ UserSettings g_userSettings = { .enableMirrorMode {"game.enableMirrorMode", false}, .disableMainHUD {"game.disableMainHUD", false}, .pauseOnFocusLost {"game.pauseOnFocusLost", false}, - .enableLinkDollRotation = {"game.enableLinkDollRotation", false }, - .enableAchievementNotifications {"game.enableAchievementNotifications", false}, + .enableLinkDollRotation = {"game.enableLinkDollRotation", false}, + .enableAchievementNotifications {"game.enableAchievementNotifications", true}, // Graphics - .bloomMode {"game.bloomMode", BloomMode::Classic}, + .bloomMode {"game.bloomMode", BloomMode::Dusk}, .bloomMultiplier {"game.bloomMultiplier", 1.0f}, .disableWaterRefraction {"game.disableWaterRefraction", false}, .enableFrameInterpolation {"game.enableFrameInterpolation", false}, @@ -114,7 +115,6 @@ UserSettings g_userSettings = { .showPipelineCompilation {"backend.showPipelineCompilation", false}, .wasPresetChosen {"backend.wasPresetChosen", false}, .enableCrashReporting {"backend.enableCrashReporting", true}, - .duskMenuOpen {"backend.duskMenuOpen", false}, .cardFileType {"backend.cardFileType", static_cast(CARD_GCIFOLDER)} } }; @@ -137,6 +137,7 @@ void registerSettings() { Register(g_userSettings.audio.fanfareVolume); Register(g_userSettings.audio.enableReverb); Register(g_userSettings.audio.enableHrtf); + Register(g_userSettings.audio.menuSounds); // Game Register(g_userSettings.game.language); @@ -212,7 +213,6 @@ void registerSettings() { Register(g_userSettings.backend.showPipelineCompilation); Register(g_userSettings.backend.wasPresetChosen); Register(g_userSettings.backend.enableCrashReporting); - Register(g_userSettings.backend.duskMenuOpen); Register(g_userSettings.backend.cardFileType); } diff --git a/src/dusk/ui/achievements.cpp b/src/dusk/ui/achievements.cpp new file mode 100644 index 0000000000..93993bb380 --- /dev/null +++ b/src/dusk/ui/achievements.cpp @@ -0,0 +1,208 @@ +#include "achievements.hpp" + +#include "Z2AudioLib/Z2SeMgr.h" +#include "dusk/achievements.h" +#include "fmt/format.h" +#include "m_Do/m_Do_audio.h" +#include "nav_types.hpp" +#include "pane.hpp" + +namespace dusk::ui { +namespace { + +struct CategoryInfo { + AchievementCategory cat; + const char* label; +}; + +constexpr CategoryInfo kCategories[] = { + {AchievementCategory::Story, "Story"}, + {AchievementCategory::Collection, "Collection"}, + {AchievementCategory::Challenge, "Challenge"}, + {AchievementCategory::Minigame, "Minigame"}, + {AchievementCategory::Misc, "Misc"}, + {AchievementCategory::Glitched, "Glitched"}, +}; + +Rml::String build_achievement_info_rml(const Achievement& a) { + Rml::String s = fmt::format( + R"(
)" + R"({})" + R"({})" + R"(
)" + R"(

{}

)", + a.unlocked ? " unlocked" : "", + a.name, + a.unlocked ? " unlocked" : " locked", + a.unlocked ? "Unlocked" : "Locked", + a.description + ); + + if (a.isCounter) { + float fraction = a.goal > 0 ? float(a.progress) / float(a.goal) : 1.0f; + s += fmt::format( + R"()" + R"({} / {})", + fraction, + a.unlocked ? "progress-done" : "progress-ongoing", + a.progress, + a.goal + ); + } + + return s; +} + +class AchievementRow : public FluentComponent { +public: + AchievementRow(Rml::Element* parent, const Achievement& a) + : FluentComponent(createRowRoot(parent)) + { + auto& btn = add_child