diff --git a/README.md b/README.md
index 85c899aeb0..02b1ded875 100644
--- a/README.md
+++ b/README.md
@@ -1,37 +1,48 @@
-
+
+

-- ### **[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!
-
+- 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"({}
)",
+ 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