achievementsettingswidget.cpp (11673B)
1 // SPDX-FileCopyrightText: 2019-2022 Connor McLaughlin <stenzek@gmail.com> 2 // SPDX-License-Identifier: (GPL-3.0 OR 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 AchievementSettingsWidget::AchievementSettingsWidget(SettingsWindow* dialog, QWidget* parent) 20 : QWidget(parent), m_dialog(dialog) 21 { 22 SettingsInterface* sif = dialog->getSettingsInterface(); 23 24 m_ui.setupUi(this); 25 26 SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.enable, "Cheevos", "Enabled", false); 27 SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.hardcoreMode, "Cheevos", "ChallengeMode", false); 28 SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.achievementNotifications, "Cheevos", "Notifications", true); 29 SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.leaderboardNotifications, "Cheevos", 30 "LeaderboardNotifications", true); 31 SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.soundEffects, "Cheevos", "SoundEffects", true); 32 SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.overlays, "Cheevos", "Overlays", true); 33 SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.encoreMode, "Cheevos", "EncoreMode", false); 34 SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.spectatorMode, "Cheevos", "SpectatorMode", false); 35 SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.unofficialAchievements, "Cheevos", "UnofficialTestMode", 36 false); 37 SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.useFirstDiscFromPlaylist, "Cheevos", 38 "UseFirstDiscFromPlaylist", true); 39 SettingWidgetBinder::BindWidgetToFloatSetting(sif, m_ui.achievementNotificationsDuration, "Cheevos", 40 "NotificationsDuration", 41 Settings::DEFAULT_ACHIEVEMENT_NOTIFICATION_TIME); 42 SettingWidgetBinder::BindWidgetToFloatSetting(sif, m_ui.leaderboardNotificationsDuration, "Cheevos", 43 "LeaderboardsDuration", 44 Settings::DEFAULT_LEADERBOARD_NOTIFICATION_TIME); 45 46 dialog->registerWidgetHelp(m_ui.enable, tr("Enable Achievements"), tr("Unchecked"), 47 tr("When enabled and logged in, DuckStation will scan for achievements on startup.")); 48 dialog->registerWidgetHelp(m_ui.hardcoreMode, tr("Enable Hardcore Mode"), tr("Unchecked"), 49 tr("\"Challenge\" mode for achievements, including leaderboard tracking. Disables save " 50 "state, cheats, and slowdown functions.")); 51 dialog->registerWidgetHelp(m_ui.achievementNotifications, tr("Show Achievement Notifications"), tr("Checked"), 52 tr("Displays popup messages on events such as achievement unlocks and game completion.")); 53 dialog->registerWidgetHelp( 54 m_ui.leaderboardNotifications, tr("Show Leaderboard Notifications"), tr("Checked"), 55 tr("Displays popup messages when starting, submitting, or failing a leaderboard challenge.")); 56 dialog->registerWidgetHelp( 57 m_ui.soundEffects, tr("Enable Sound Effects"), tr("Checked"), 58 tr("Plays sound effects for events such as achievement unlocks and leaderboard submissions.")); 59 dialog->registerWidgetHelp( 60 m_ui.overlays, tr("Enable In-Game Overlays"), tr("Checked"), 61 tr("Shows icons in the lower-right corner of the screen when a challenge/primed achievement is active.")); 62 dialog->registerWidgetHelp(m_ui.encoreMode, tr("Enable Encore Mode"), tr("Unchecked"), 63 tr("When enabled, each session will behave as if no achievements have been unlocked.")); 64 dialog->registerWidgetHelp(m_ui.spectatorMode, tr("Enable Spectator Mode"), tr("Unchecked"), 65 tr("When enabled, DuckStation will assume all achievements are locked and not send any " 66 "unlock notifications to the server.")); 67 dialog->registerWidgetHelp( 68 m_ui.unofficialAchievements, tr("Test Unofficial Achievements"), tr("Unchecked"), 69 tr("When enabled, DuckStation will list achievements from unofficial sets. Please note that these achievements are " 70 "not tracked by RetroAchievements, so they unlock every time.")); 71 dialog->registerWidgetHelp( 72 m_ui.useFirstDiscFromPlaylist, tr("Use First Disc From Playlist"), tr("Unchecked"), 73 tr( 74 "When enabled, the first disc in a playlist will be used for achievements, regardless of which disc is active.")); 75 76 connect(m_ui.enable, &QCheckBox::checkStateChanged, this, &AchievementSettingsWidget::updateEnableState); 77 connect(m_ui.hardcoreMode, &QCheckBox::checkStateChanged, this, &AchievementSettingsWidget::updateEnableState); 78 connect(m_ui.hardcoreMode, &QCheckBox::checkStateChanged, this, &AchievementSettingsWidget::onHardcoreModeStateChanged); 79 connect(m_ui.achievementNotifications, &QCheckBox::checkStateChanged, this, &AchievementSettingsWidget::updateEnableState); 80 connect(m_ui.leaderboardNotifications, &QCheckBox::checkStateChanged, this, &AchievementSettingsWidget::updateEnableState); 81 connect(m_ui.achievementNotificationsDuration, &QSlider::valueChanged, this, 82 &AchievementSettingsWidget::onAchievementsNotificationDurationSliderChanged); 83 connect(m_ui.leaderboardNotificationsDuration, &QSlider::valueChanged, this, 84 &AchievementSettingsWidget::onLeaderboardsNotificationDurationSliderChanged); 85 86 if (!m_dialog->isPerGameSettings()) 87 { 88 connect(m_ui.loginButton, &QPushButton::clicked, this, &AchievementSettingsWidget::onLoginLogoutPressed); 89 connect(m_ui.viewProfile, &QPushButton::clicked, this, &AchievementSettingsWidget::onViewProfilePressed); 90 connect(g_emu_thread, &EmuThread::achievementsRefreshed, this, &AchievementSettingsWidget::onAchievementsRefreshed); 91 updateLoginState(); 92 93 // force a refresh of game info 94 Host::RunOnCPUThread(Host::OnAchievementsRefreshed); 95 } 96 else 97 { 98 // remove login and game info, not relevant for per-game 99 m_ui.verticalLayout->removeWidget(m_ui.gameInfoBox); 100 m_ui.gameInfoBox->deleteLater(); 101 m_ui.gameInfoBox = nullptr; 102 m_ui.verticalLayout->removeWidget(m_ui.loginBox); 103 m_ui.loginBox->deleteLater(); 104 m_ui.loginBox = nullptr; 105 } 106 107 updateEnableState(); 108 onAchievementsNotificationDurationSliderChanged(); 109 onLeaderboardsNotificationDurationSliderChanged(); 110 } 111 112 AchievementSettingsWidget::~AchievementSettingsWidget() = default; 113 114 void AchievementSettingsWidget::updateEnableState() 115 { 116 const bool enabled = m_dialog->getEffectiveBoolValue("Cheevos", "Enabled", false); 117 const bool notifications = enabled && m_dialog->getEffectiveBoolValue("Cheevos", "Notifications", true); 118 const bool lb_notifications = enabled && m_dialog->getEffectiveBoolValue("Cheevos", "LeaderboardNotifications", true); 119 m_ui.hardcoreMode->setEnabled(enabled); 120 m_ui.achievementNotifications->setEnabled(enabled); 121 m_ui.leaderboardNotifications->setEnabled(enabled); 122 m_ui.achievementNotificationsDuration->setEnabled(notifications); 123 m_ui.achievementNotificationsDurationLabel->setEnabled(notifications); 124 m_ui.leaderboardNotificationsDuration->setEnabled(lb_notifications); 125 m_ui.leaderboardNotificationsDurationLabel->setEnabled(lb_notifications); 126 m_ui.soundEffects->setEnabled(enabled); 127 m_ui.overlays->setEnabled(enabled); 128 m_ui.encoreMode->setEnabled(enabled); 129 m_ui.spectatorMode->setEnabled(enabled); 130 m_ui.unofficialAchievements->setEnabled(enabled); 131 m_ui.useFirstDiscFromPlaylist->setEnabled(enabled); 132 } 133 134 void AchievementSettingsWidget::onHardcoreModeStateChanged() 135 { 136 if (!QtHost::IsSystemValid()) 137 return; 138 139 const bool enabled = m_dialog->getEffectiveBoolValue("Cheevos", "Enabled", false); 140 const bool challenge = m_dialog->getEffectiveBoolValue("Cheevos", "ChallengeMode", false); 141 if (!enabled || !challenge) 142 return; 143 144 // don't bother prompting if the game doesn't have achievements 145 auto lock = Achievements::GetLock(); 146 if (!Achievements::HasActiveGame()) 147 return; 148 149 if (QMessageBox::question( 150 QtUtils::GetRootWidget(this), tr("Reset System"), 151 tr("Hardcore mode will not be enabled until the system is reset. Do you want to reset the system now?")) != 152 QMessageBox::Yes) 153 { 154 return; 155 } 156 157 g_emu_thread->resetSystem(true); 158 } 159 160 void AchievementSettingsWidget::onAchievementsNotificationDurationSliderChanged() 161 { 162 const int duration = 163 m_dialog->getEffectiveIntValue("Cheevos", "NotificationsDuration", Settings::DEFAULT_ACHIEVEMENT_NOTIFICATION_TIME); 164 m_ui.achievementNotificationsDurationLabel->setText(tr("%n seconds", nullptr, duration)); 165 } 166 167 void AchievementSettingsWidget::onLeaderboardsNotificationDurationSliderChanged() 168 { 169 const int duration = 170 m_dialog->getEffectiveIntValue("Cheevos", "LeaderboardsDuration", Settings::DEFAULT_LEADERBOARD_NOTIFICATION_TIME); 171 m_ui.leaderboardNotificationsDurationLabel->setText(tr("%n seconds", nullptr, duration)); 172 } 173 174 void AchievementSettingsWidget::updateLoginState() 175 { 176 const std::string username(Host::GetBaseStringSettingValue("Cheevos", "Username")); 177 const bool logged_in = !username.empty(); 178 179 if (logged_in) 180 { 181 const u64 login_unix_timestamp = 182 StringUtil::FromChars<u64>(Host::GetBaseStringSettingValue("Cheevos", "LoginTimestamp", "0")).value_or(0); 183 const QDateTime login_timestamp(QDateTime::fromSecsSinceEpoch(static_cast<qint64>(login_unix_timestamp))); 184 m_ui.loginStatus->setText(tr("Username: %1\nLogin token generated on %2.") 185 .arg(QString::fromStdString(username)) 186 .arg(login_timestamp.toString(Qt::TextDate))); 187 m_ui.loginButton->setText(tr("Logout")); 188 } 189 else 190 { 191 m_ui.loginStatus->setText(tr("Not Logged In.")); 192 m_ui.loginButton->setText(tr("Login...")); 193 } 194 195 m_ui.viewProfile->setEnabled(logged_in); 196 } 197 198 void AchievementSettingsWidget::onLoginLogoutPressed() 199 { 200 if (!Host::GetBaseStringSettingValue("Cheevos", "Username").empty()) 201 { 202 Host::RunOnCPUThread([]() { Achievements::Logout(); }, true); 203 updateLoginState(); 204 return; 205 } 206 207 AchievementLoginDialog login(this, Achievements::LoginRequestReason::UserInitiated); 208 int res = login.exec(); 209 if (res != 0) 210 return; 211 212 updateLoginState(); 213 214 // Login can enable achievements/hardcore. 215 if (!m_ui.enable->isChecked() && Host::GetBaseBoolSettingValue("Cheevos", "Enabled", false)) 216 { 217 QSignalBlocker sb(m_ui.enable); 218 m_ui.enable->setChecked(true); 219 updateEnableState(); 220 } 221 if (!m_ui.hardcoreMode->isChecked() && Host::GetBaseBoolSettingValue("Cheevos", "ChallengeMode", false)) 222 { 223 QSignalBlocker sb(m_ui.hardcoreMode); 224 m_ui.hardcoreMode->setChecked(true); 225 } 226 } 227 228 void AchievementSettingsWidget::onViewProfilePressed() 229 { 230 const std::string username(Host::GetBaseStringSettingValue("Cheevos", "Username")); 231 if (username.empty()) 232 return; 233 234 const QByteArray encoded_username(QUrl::toPercentEncoding(QString::fromStdString(username))); 235 QtUtils::OpenURL( 236 QtUtils::GetRootWidget(this), 237 QUrl(QStringLiteral("https://retroachievements.org/user/%1").arg(QString::fromUtf8(encoded_username)))); 238 } 239 240 void AchievementSettingsWidget::onAchievementsRefreshed(quint32 id, const QString& game_info_string) 241 { 242 m_ui.gameInfo->setText(game_info_string); 243 }