Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
stenzek
GitHub Repository: stenzek/duckstation
Path: blob/master/src/duckstation-qt/achievementsettingswidget.cpp
4242 views
1
// SPDX-FileCopyrightText: 2019-2025 Connor McLaughlin <[email protected]>
2
// SPDX-License-Identifier: CC-BY-NC-ND-4.0
3
4
#include "achievementsettingswidget.h"
5
#include "achievementlogindialog.h"
6
#include "mainwindow.h"
7
#include "qtutils.h"
8
#include "settingswindow.h"
9
#include "settingwidgetbinder.h"
10
11
#include "core/achievements.h"
12
#include "core/system.h"
13
14
#include "common/string_util.h"
15
16
#include <QtCore/QDateTime>
17
#include <QtWidgets/QMessageBox>
18
19
#include "moc_achievementsettingswidget.cpp"
20
21
AchievementSettingsWidget::AchievementSettingsWidget(SettingsWindow* dialog, QWidget* parent)
22
: QWidget(parent), m_dialog(dialog)
23
{
24
SettingsInterface* sif = dialog->getSettingsInterface();
25
26
m_ui.setupUi(this);
27
28
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.enable, "Cheevos", "Enabled", false);
29
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.hardcoreMode, "Cheevos", "ChallengeMode", false);
30
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.encoreMode, "Cheevos", "EncoreMode", false);
31
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.spectatorMode, "Cheevos", "SpectatorMode", false);
32
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.unofficialAchievements, "Cheevos", "UnofficialTestMode",
33
false);
34
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.achievementNotifications, "Cheevos", "Notifications", true);
35
SettingWidgetBinder::BindWidgetToFloatSetting(sif, m_ui.achievementNotificationsDuration, "Cheevos",
36
"NotificationsDuration",
37
Settings::DEFAULT_ACHIEVEMENT_NOTIFICATION_TIME);
38
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.leaderboardNotifications, "Cheevos",
39
"LeaderboardNotifications", true);
40
SettingWidgetBinder::BindWidgetToFloatSetting(sif, m_ui.leaderboardNotificationsDuration, "Cheevos",
41
"LeaderboardsDuration",
42
Settings::DEFAULT_LEADERBOARD_NOTIFICATION_TIME);
43
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.leaderboardTrackers, "Cheevos", "LeaderboardTrackers", true);
44
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.soundEffects, "Cheevos", "SoundEffects", true);
45
SettingWidgetBinder::BindWidgetToEnumSetting(
46
sif, m_ui.challengeIndicatorMode, "Cheevos", "ChallengeIndicatorMode",
47
&Settings::ParseAchievementChallengeIndicatorMode, &Settings::GetAchievementChallengeIndicatorModeName,
48
&Settings::GetAchievementChallengeIndicatorModeDisplayName, Settings::DEFAULT_ACHIEVEMENT_CHALLENGE_INDICATOR_MODE,
49
AchievementChallengeIndicatorMode::MaxCount);
50
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.progressIndicators, "Cheevos", "ProgressIndicators", true);
51
52
dialog->registerWidgetHelp(m_ui.enable, tr("Enable Achievements"), tr("Unchecked"),
53
tr("When enabled and logged in, DuckStation will scan for achievements on startup."));
54
dialog->registerWidgetHelp(m_ui.hardcoreMode, tr("Enable Hardcore Mode"), tr("Unchecked"),
55
tr("\"Challenge\" mode for achievements, including leaderboard tracking. Disables save "
56
"state, cheats, and slowdown functions."));
57
dialog->registerWidgetHelp(m_ui.encoreMode, tr("Enable Encore Mode"), tr("Unchecked"),
58
tr("When enabled, each session will behave as if no achievements have been unlocked."));
59
dialog->registerWidgetHelp(m_ui.spectatorMode, tr("Enable Spectator Mode"), tr("Unchecked"),
60
tr("When enabled, DuckStation will assume all achievements are locked and not send any "
61
"unlock notifications to the server."));
62
dialog->registerWidgetHelp(
63
m_ui.unofficialAchievements, tr("Test Unofficial Achievements"), tr("Unchecked"),
64
tr("When enabled, DuckStation will list achievements from unofficial sets. Please note that these achievements are "
65
"not tracked by RetroAchievements, so they unlock every time."));
66
dialog->registerWidgetHelp(m_ui.achievementNotifications, tr("Show Achievement Notifications"), tr("Checked"),
67
tr("Displays popup messages on events such as achievement unlocks and game completion."));
68
dialog->registerWidgetHelp(
69
m_ui.leaderboardNotifications, tr("Show Leaderboard Notifications"), tr("Checked"),
70
tr("Displays popup messages when starting, submitting, or failing a leaderboard challenge."));
71
dialog->registerWidgetHelp(
72
m_ui.leaderboardTrackers, tr("Show Leaderboard Trackers"), tr("Checked"),
73
tr("Shows a timer in the bottom-right corner of the screen when leaderboard challenges are active."));
74
dialog->registerWidgetHelp(
75
m_ui.soundEffects, tr("Enable Sound Effects"), tr("Checked"),
76
tr("Plays sound effects for events such as achievement unlocks and leaderboard submissions."));
77
dialog->registerWidgetHelp(m_ui.challengeIndicatorMode, tr("Challenge Indicators"), tr("Show Persistent Icons"),
78
tr("Shows a notification or icons in the lower-right corner of the screen when a "
79
"challenge/primed achievement is active."));
80
dialog->registerWidgetHelp(
81
m_ui.progressIndicators, tr("Show Progress Indicators"), tr("Checked"),
82
tr("Shows a popup in the lower-right corner of the screen when progress towards a measured achievement changes."));
83
84
connect(m_ui.enable, &QCheckBox::checkStateChanged, this, &AchievementSettingsWidget::updateEnableState);
85
connect(m_ui.hardcoreMode, &QCheckBox::checkStateChanged, this,
86
&AchievementSettingsWidget::onHardcoreModeStateChanged);
87
connect(m_ui.achievementNotifications, &QCheckBox::checkStateChanged, this,
88
&AchievementSettingsWidget::updateEnableState);
89
connect(m_ui.leaderboardNotifications, &QCheckBox::checkStateChanged, this,
90
&AchievementSettingsWidget::updateEnableState);
91
connect(m_ui.achievementNotificationsDuration, &QSlider::valueChanged, this,
92
&AchievementSettingsWidget::onAchievementsNotificationDurationSliderChanged);
93
connect(m_ui.leaderboardNotificationsDuration, &QSlider::valueChanged, this,
94
&AchievementSettingsWidget::onLeaderboardsNotificationDurationSliderChanged);
95
96
if (!m_dialog->isPerGameSettings())
97
{
98
connect(m_ui.loginButton, &QPushButton::clicked, this, &AchievementSettingsWidget::onLoginLogoutPressed);
99
connect(m_ui.viewProfile, &QPushButton::clicked, this, &AchievementSettingsWidget::onViewProfilePressed);
100
connect(m_ui.refreshProgress, &QPushButton::clicked, g_emu_thread, &EmuThread::refreshAchievementsAllProgress);
101
connect(g_emu_thread, &EmuThread::achievementsRefreshed, this, &AchievementSettingsWidget::onAchievementsRefreshed);
102
updateLoginState();
103
104
// force a refresh of game info
105
Host::RunOnCPUThread(Host::OnAchievementsRefreshed);
106
}
107
else
108
{
109
// remove login and game info, not relevant for per-game
110
m_ui.verticalLayout->removeWidget(m_ui.gameInfoBox);
111
m_ui.gameInfoBox->deleteLater();
112
m_ui.gameInfoBox = nullptr;
113
m_ui.verticalLayout->removeWidget(m_ui.loginBox);
114
m_ui.loginBox->deleteLater();
115
m_ui.loginBox = nullptr;
116
}
117
118
// RAIntegration is not available on non-win32/x64.
119
#ifdef RC_CLIENT_SUPPORTS_RAINTEGRATION
120
if (Achievements::IsRAIntegrationAvailable())
121
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.useRAIntegration, "Cheevos", "UseRAIntegration", false);
122
else
123
m_ui.useRAIntegration->setEnabled(false);
124
125
dialog->registerWidgetHelp(
126
m_ui.useRAIntegration, tr("Enable RAIntegration (Development Only)"), tr("Unchecked"),
127
tr("When enabled, DuckStation will load the RAIntegration DLL which allows for achievement development.<br>The "
128
"RA_Integration.dll file must be placed in the same directory as the DuckStation executable."));
129
#else
130
m_ui.settingsLayout->removeWidget(m_ui.useRAIntegration);
131
delete m_ui.useRAIntegration;
132
m_ui.useRAIntegration = nullptr;
133
#endif
134
135
updateEnableState();
136
onAchievementsNotificationDurationSliderChanged();
137
onLeaderboardsNotificationDurationSliderChanged();
138
}
139
140
AchievementSettingsWidget::~AchievementSettingsWidget() = default;
141
142
void AchievementSettingsWidget::updateEnableState()
143
{
144
const bool enabled = m_dialog->getEffectiveBoolValue("Cheevos", "Enabled", false);
145
const bool notifications = enabled && m_dialog->getEffectiveBoolValue("Cheevos", "Notifications", true);
146
const bool lb_notifications = enabled && m_dialog->getEffectiveBoolValue("Cheevos", "LeaderboardNotifications", true);
147
m_ui.hardcoreMode->setEnabled(enabled);
148
m_ui.achievementNotifications->setEnabled(enabled);
149
m_ui.leaderboardNotifications->setEnabled(enabled);
150
m_ui.achievementNotificationsDuration->setEnabled(notifications);
151
m_ui.achievementNotificationsDurationLabel->setEnabled(notifications);
152
m_ui.leaderboardNotificationsDuration->setEnabled(lb_notifications);
153
m_ui.leaderboardNotificationsDurationLabel->setEnabled(lb_notifications);
154
m_ui.leaderboardTrackers->setEnabled(enabled);
155
m_ui.soundEffects->setEnabled(enabled);
156
m_ui.challengeIndicatorMode->setEnabled(enabled);
157
m_ui.challengeIndicatorModeLabel->setEnabled(enabled);
158
m_ui.progressIndicators->setEnabled(enabled);
159
m_ui.encoreMode->setEnabled(enabled);
160
m_ui.spectatorMode->setEnabled(enabled);
161
m_ui.unofficialAchievements->setEnabled(enabled);
162
if (!m_dialog->isPerGameSettings())
163
m_ui.refreshProgress->setEnabled(enabled && m_ui.viewProfile->isEnabled());
164
}
165
166
void AchievementSettingsWidget::onHardcoreModeStateChanged()
167
{
168
if (!QtHost::IsSystemValid())
169
return;
170
171
const bool enabled = m_dialog->getEffectiveBoolValue("Cheevos", "Enabled", false);
172
const bool challenge = m_dialog->getEffectiveBoolValue("Cheevos", "ChallengeMode", false);
173
if (!enabled || !challenge)
174
return;
175
176
// don't bother prompting if the game doesn't have achievements
177
{
178
auto lock = Achievements::GetLock();
179
if (!Achievements::HasActiveGame())
180
return;
181
}
182
183
if (QMessageBox::question(
184
QtUtils::GetRootWidget(this), tr("Reset System"),
185
tr("Hardcore mode will not be enabled until the system is reset. Do you want to reset the system now?")) !=
186
QMessageBox::Yes)
187
{
188
return;
189
}
190
191
g_emu_thread->resetSystem(true);
192
}
193
194
void AchievementSettingsWidget::onAchievementsNotificationDurationSliderChanged()
195
{
196
const int duration =
197
m_dialog->getEffectiveIntValue("Cheevos", "NotificationsDuration", Settings::DEFAULT_ACHIEVEMENT_NOTIFICATION_TIME);
198
m_ui.achievementNotificationsDurationLabel->setText(tr("%n seconds", nullptr, duration));
199
}
200
201
void AchievementSettingsWidget::onLeaderboardsNotificationDurationSliderChanged()
202
{
203
const int duration =
204
m_dialog->getEffectiveIntValue("Cheevos", "LeaderboardsDuration", Settings::DEFAULT_LEADERBOARD_NOTIFICATION_TIME);
205
m_ui.leaderboardNotificationsDurationLabel->setText(tr("%n seconds", nullptr, duration));
206
}
207
208
void AchievementSettingsWidget::updateLoginState()
209
{
210
const std::string username(Host::GetBaseStringSettingValue("Cheevos", "Username"));
211
const bool logged_in = !username.empty();
212
213
if (logged_in)
214
{
215
const u64 login_unix_timestamp =
216
StringUtil::FromChars<u64>(Host::GetBaseStringSettingValue("Cheevos", "LoginTimestamp", "0")).value_or(0);
217
const QString login_timestamp =
218
QtHost::FormatNumber(Host::NumberFormatType::ShortDateTime, static_cast<s64>(login_unix_timestamp));
219
m_ui.loginStatus->setText(
220
tr("Username: %1\nLogin token generated on %2.").arg(QString::fromStdString(username)).arg(login_timestamp));
221
m_ui.loginButton->setText(tr("Logout"));
222
}
223
else
224
{
225
m_ui.loginStatus->setText(tr("Not Logged In."));
226
m_ui.loginButton->setText(tr("Login..."));
227
}
228
229
m_ui.viewProfile->setEnabled(logged_in);
230
m_ui.refreshProgress->setEnabled(logged_in && Host::GetBaseBoolSettingValue("Cheevos", "Enabled", false));
231
}
232
233
void AchievementSettingsWidget::onLoginLogoutPressed()
234
{
235
if (!Host::GetBaseStringSettingValue("Cheevos", "Username").empty())
236
{
237
Host::RunOnCPUThread([]() { Achievements::Logout(); }, true);
238
updateLoginState();
239
return;
240
}
241
242
AchievementLoginDialog login(this, Achievements::LoginRequestReason::UserInitiated);
243
if (login.exec() == QDialog::Rejected)
244
return;
245
246
updateLoginState();
247
248
// Login can enable achievements/hardcore.
249
if (!m_ui.enable->isChecked() && Host::GetBaseBoolSettingValue("Cheevos", "Enabled", false))
250
{
251
QSignalBlocker sb(m_ui.enable);
252
m_ui.enable->setChecked(true);
253
updateEnableState();
254
}
255
if (!m_ui.hardcoreMode->isChecked() && Host::GetBaseBoolSettingValue("Cheevos", "ChallengeMode", false))
256
{
257
QSignalBlocker sb(m_ui.hardcoreMode);
258
m_ui.hardcoreMode->setChecked(true);
259
}
260
}
261
262
void AchievementSettingsWidget::onViewProfilePressed()
263
{
264
const std::string username(Host::GetBaseStringSettingValue("Cheevos", "Username"));
265
if (username.empty())
266
return;
267
268
const QByteArray encoded_username(QUrl::toPercentEncoding(QString::fromStdString(username)));
269
QtUtils::OpenURL(
270
QtUtils::GetRootWidget(this),
271
QUrl(QStringLiteral("https://retroachievements.org/user/%1").arg(QString::fromUtf8(encoded_username))));
272
}
273
274
void AchievementSettingsWidget::onAchievementsRefreshed(quint32 id, const QString& game_info_string)
275
{
276
m_ui.gameInfo->setText(game_info_string);
277
}
278
279