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