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 0d05404564..2541890ebf 160000 --- a/extern/aurora +++ b/extern/aurora @@ -1 +1 @@ -Subproject commit 0d054045646dbc0033a768243ccc160ab02214c1 +Subproject commit 2541890ebf0a44b78e12c0fed384a24fe6be1221 diff --git a/files.cmake b/files.cmake index 764c79e5ae..d543ec869f 100644 --- a/files.cmake +++ b/files.cmake @@ -1451,8 +1451,6 @@ set(DUSK_FILES src/dusk/imgui/ImGuiMenuRandomizer.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 @@ -1463,8 +1461,6 @@ 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 @@ -1475,6 +1471,10 @@ 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 diff --git a/include/d/d_camera.h b/include/d/d_camera.h index 105c9cae9f..c481262a46 100644 --- a/include/d/d_camera.h +++ b/include/d/d_camera.h @@ -118,6 +118,18 @@ class camera_class; class dCamera_c; typedef bool (dCamera_c::*engine_fn)(s32); +#if TARGET_PC +struct DebugFlyCam { + bool initialized; + f32 pitch; + f32 yaw; + cXyz savedCenter; + cXyz savedEye; + f32 savedFovy; + cSAngle savedBank; +}; +#endif + class dCamera_c { public: class dCamInfo_c { @@ -1028,6 +1040,8 @@ public: bool test2Camera(s32); #if TARGET_PC bool freeCamera(); + bool executeDebugFlyCam(); + void deactivateDebugFlyCam(); #endif bool towerCamera(s32); bool hookshotCamera(s32); @@ -1376,6 +1390,10 @@ public: /* 0x970 */ dCamSetup_c mCamSetup; /* 0xAEC */ dCamParam_c mCamParam; /* 0xB0C */ u8 field_0xb0c; + +#if TARGET_PC + DebugFlyCam mDebugFlyCam; +#endif }; // Size: 0xB10 dCamera_c* dCam_getBody(); diff --git a/include/dusk/settings.h b/include/dusk/settings.h index e11647e8e4..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 @@ -119,6 +120,7 @@ struct UserSettings { ConfigVar invertCameraXAxis; ConfigVar invertCameraYAxis; ConfigVar freeCameraSensitivity; + ConfigVar debugFlyCam; // Cheats ConfigVar infiniteHearts; @@ -154,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/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/window.rcss b/res/rml/window.rcss index 17e4dec6f3..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,11 +235,12 @@ 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; } @@ -241,3 +252,150 @@ 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/d_camera.cpp b/src/d/d_camera.cpp index d74cf24c43..e559a56d95 100644 --- a/src/d/d_camera.cpp +++ b/src/d/d_camera.cpp @@ -1040,6 +1040,11 @@ void dCamera_c::debugDrawInit() { bool dCamera_c::Run() { #if TARGET_PC ResetView(); + if (executeDebugFlyCam()) { + mFrameCounter++; + mTicks++; + return true; + } #endif daAlink_c* link = daAlink_getAlinkActorClass(); @@ -7474,7 +7479,104 @@ bool dCamera_c::test2Camera(s32 param_0) { return false; } +static constexpr f32 FLYCAM_SPEED = 0.5f; +static constexpr f32 FLYCAM_FAST_SPEED = 4.0f; +static constexpr f32 FLYCAM_ROTATION_SPEED = 0.002f; +static constexpr f32 FLYCAM_TRIGGER_DEADZONE = 20.0f; + #if TARGET_PC +bool dCamera_c::executeDebugFlyCam() { + if (!dusk::getSettings().game.debugFlyCam) { + if (mDebugFlyCam.initialized) { + deactivateDebugFlyCam(); + } + return false; + } + + dEvt_control_c* event = dComIfGp_getEvent(); + if (event == nullptr) { + return false; + } + + if (!mDebugFlyCam.initialized && (event->mEventStatus != 0 || dComIfGp_isPauseFlag())) { + dusk::getSettings().game.debugFlyCam.setValue(false); + return false; + } + + if (!mDebugFlyCam.initialized) { + mDebugFlyCam.savedCenter = mCenter; + mDebugFlyCam.savedEye = mEye; + mDebugFlyCam.savedFovy = mFovy; + mDebugFlyCam.savedBank = mBank; + + f32 dx = mCenter.x - mEye.x; + f32 dy = mCenter.y - mEye.y; + f32 dz = mCenter.z - mEye.z; + mDebugFlyCam.yaw = atan2f(dz, dx); + f32 horizontal = sqrtf(dx * dx + dz * dz); + mDebugFlyCam.pitch = atan2f(dy, horizontal); + + mDebugFlyCam.initialized = true; + } + + event->mEventStatus = 1; + dComIfGp_getEventManager().setCameraPlay(1); + + interface_of_controller_pad& pad = mDoCPd_c::getCpadInfo(0); + f32 stickY = pad.mMainStickPosY * 72.0f; + f32 stickX = pad.mMainStickPosX * 72.0f; + f32 cStickY = pad.mCStickPosY * 59.0f; + f32 cStickX = pad.mCStickPosX * 59.0f; + f32 trigL = pad.mTriggerLeft * 150.0f; + f32 trigR = pad.mTriggerRight * 150.0f; + + f32 verticalDisp = 0.0f; + if (trigR >= FLYCAM_TRIGGER_DEADZONE) { + verticalDisp += trigR; + } + if (trigL >= FLYCAM_TRIGGER_DEADZONE) { + verticalDisp -= trigL; + } + + f32 moveDy = stickY * sinf(mDebugFlyCam.pitch) + verticalDisp; + f32 moveDx = stickY * cosf(mDebugFlyCam.yaw) * cosf(mDebugFlyCam.pitch) - stickX * sinf(mDebugFlyCam.yaw); + f32 moveDz = stickY * sinf(mDebugFlyCam.yaw) * cosf(mDebugFlyCam.pitch) + stickX * cosf(mDebugFlyCam.yaw); + + f32 speed = mDoCPd_c::getHoldZ(PAD_1) ? FLYCAM_FAST_SPEED : FLYCAM_SPEED; + + mEye.x += speed * moveDx; + mEye.y += speed * moveDy; + mEye.z += speed * moveDz; + + static constexpr f32 FLYCAM_TARGET_DIST = 100.0f; + mCenter.x = mEye.x + cosf(mDebugFlyCam.yaw) * cosf(mDebugFlyCam.pitch) * FLYCAM_TARGET_DIST; + mCenter.z = mEye.z + sinf(mDebugFlyCam.yaw) * cosf(mDebugFlyCam.pitch) * FLYCAM_TARGET_DIST; + mCenter.y = mEye.y + sinf(mDebugFlyCam.pitch) * FLYCAM_TARGET_DIST; + + Reset(mCenter, mEye); + + f32 yawInput = dusk::getSettings().game.invertCameraXAxis ? cStickX : -cStickX; + mDebugFlyCam.yaw += yawInput * FLYCAM_ROTATION_SPEED; + mDebugFlyCam.yaw = fmodf(mDebugFlyCam.yaw + 2.0f * (f32)M_PI, 2.0f * (f32)M_PI); + + f32 maxPitch = (f32)M_PI / 2.0f - 0.1f; + f32 minPitch = -(f32)M_PI / 2.0f + 0.1f; + mDebugFlyCam.pitch = std::clamp(mDebugFlyCam.pitch + cStickY * FLYCAM_ROTATION_SPEED, minPitch, maxPitch); + + return true; +} + +void dCamera_c::deactivateDebugFlyCam() { + Reset(mDebugFlyCam.savedCenter, mDebugFlyCam.savedEye, mDebugFlyCam.savedFovy, mDebugFlyCam.savedBank.Val()); + + dEvt_control_c* event = dComIfGp_getEvent(); + if (event != nullptr) { + event->mEventStatus = 0; + } + dComIfGp_getEventManager().setCameraPlay(0); + mDebugFlyCam.initialized = false; +} + bool dCamera_c::freeCamera() { if (dusk::getSettings().game.freeCamera && mGear == 1) { mGear = 0; 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/ImGuiCameraOverlay.cpp b/src/dusk/imgui/ImGuiCameraOverlay.cpp index aa3d1ac093..2a39ef85eb 100644 --- a/src/dusk/imgui/ImGuiCameraOverlay.cpp +++ b/src/dusk/imgui/ImGuiCameraOverlay.cpp @@ -1,9 +1,12 @@ #include "f_op/f_op_camera_mng.h" #include "SSystem/SComponent/c_xyz.h" +#include "d/d_com_inf_game.h" #include "imgui.h" +#include "ImGuiConfig.hpp" #include "ImGuiConsole.hpp" #include "ImGuiMenuTools.hpp" +#include "dusk/settings.h" namespace dusk { void ImGuiMenuTools::ShowCameraOverlay() { @@ -46,70 +49,25 @@ namespace dusk { ImGui::InputFloat("Camera FOV", &dCam->mFovy); - ImGui::SeparatorText("Free-look Data"); + ImGui::SeparatorText("Options"); - static float eyeYawDeg = 0.0f; - static float moveSpeed = 5000.0f; - static float rotSpeed = 5.0f; - static cXyz freeLookPos = cXyz::Zero; - static bool freeLookActive = false; - - bool changed = false; - - if (ImGui::IsKeyDown(ImGuiKey_LeftArrow)) { - eyeYawDeg += rotSpeed; - if (eyeYawDeg >= 360.0f) - eyeYawDeg -= 360.0f; - - changed = true; + bool eventRunning = (dComIfGp_event_runCheck() || dComIfGp_isPauseFlag()) && !getSettings().game.debugFlyCam; + if (eventRunning) { + ImGui::BeginDisabled(); } - else if (ImGui::IsKeyDown(ImGuiKey_RightArrow)) { - eyeYawDeg -= rotSpeed; - if (eyeYawDeg < 0.0f) - eyeYawDeg += 360.0f; - - changed = true; + config::ImGuiCheckbox("Fly Mode", getSettings().game.debugFlyCam); + if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) { + if (eventRunning) { + ImGui::SetTooltip("Cannot enable while paused or during an active event."); + } else { + ImGui::SetTooltip("Detach camera and fly freely.\n" + "Left stick: move, C-stick: look\n" + "L/R triggers: up/down, Z: fast"); + } } - cSAngle yawAngle = cSAngle(eyeYawDeg); - cXyz frontDir = cXyz(yawAngle.Sin(), 0.0f, yawAngle.Cos()); - - if (ImGui::IsKeyDown(ImGuiKey_UpArrow)) { - freeLookPos -= frontDir * moveSpeed * ImGui::GetIO().DeltaTime; - changed = true; + if (eventRunning) { + ImGui::EndDisabled(); } - else if (ImGui::IsKeyDown(ImGuiKey_DownArrow)) { - freeLookPos += frontDir * moveSpeed * ImGui::GetIO().DeltaTime; - changed = true; - } - - if (ImGui::IsKeyDown(ImGuiKey_LeftShift)) { - freeLookPos += cXyz::BaseY * moveSpeed * ImGui::GetIO().DeltaTime; - changed = true; - } - - if (ImGui::IsKeyDown(ImGuiKey_LeftCtrl)) { - freeLookPos -= cXyz::BaseY * moveSpeed * ImGui::GetIO().DeltaTime; - changed = true; - } - - if (!freeLookActive && changed) { - freeLookPos += dCam->Center(); - freeLookActive = true; - } - - if (ImGui::IsKeyDown(ImGuiKey_R)) { - freeLookPos = cXyz::Zero; - freeLookActive = false; - } - - if (freeLookActive) { - dCam->Reset(freeLookPos, freeLookPos + (frontDir * 100.0f)); - } - - ImGui::InputFloat("Free-look Yaw", &eyeYawDeg); - ImGui::InputFloat3("Free-look Position", &freeLookPos.x); - ImGui::InputFloat("Free-look Move Speed", &moveSpeed); - ImGui::InputFloat("Free-look Rotation Speed", &rotSpeed); ShowCornerContextMenu(m_cameraOverlayCorner, 0); diff --git a/src/dusk/imgui/ImGuiConsole.cpp b/src/dusk/imgui/ImGuiConsole.cpp index 53c595c21c..68c11efb10 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; @@ -324,15 +278,10 @@ namespace dusk { // 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 @@ -356,14 +305,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; @@ -399,10 +343,9 @@ namespace dusk { m_menuTools.ShowSaveEditor(); m_menuTools.ShowStateShare(); } - m_menuTools.ShowStateShare(); m_menuRandomizer.windowRandoStats(); m_menuRandomizer.windowRandoGeneration(); - 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 88e4b929c5..73b52f04c1 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 "ImGuiMenuRandomizer.hpp" @@ -43,15 +41,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; ImGuiMenuRandomizer m_menuRandomizer; 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/ImGuiMenuGame.cpp b/src/dusk/imgui/ImGuiMenuGame.cpp index c70c55c2fb..616e0ddbac 100644 --- a/src/dusk/imgui/ImGuiMenuGame.cpp +++ b/src/dusk/imgui/ImGuiMenuGame.cpp @@ -601,6 +601,7 @@ namespace dusk { getSettings().game.freeMagicArmor.setValue(false); getSettings().game.enableTurboKeybind.setValue(false); + getSettings().game.debugFlyCam.setValue(false); } SpeedrunInfo m_speedrunInfo; 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 49df869dd4..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}, @@ -78,6 +79,7 @@ UserSettings g_userSettings = { .invertCameraXAxis {"game.invertCameraXAxis", false}, .invertCameraYAxis {"game.invertCameraYAxis", false}, .freeCameraSensitivity {"game.freeCameraSensitivity", 1.0f}, + .debugFlyCam {"game.debugFlyCam", false}, // Cheats .infiniteHearts {"game.infiniteHearts", false}, @@ -113,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)} } }; @@ -136,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); @@ -203,6 +205,7 @@ void registerSettings() { Register(g_userSettings.game.gyroInvertPitch); Register(g_userSettings.game.gyroInvertYaw); Register(g_userSettings.game.freeCamera); + Register(g_userSettings.game.debugFlyCam); Register(g_userSettings.backend.isoPath); Register(g_userSettings.backend.graphicsBackend); @@ -210,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