2022-12-04 11:03:45 +00:00
|
|
|
// SPDX-FileCopyrightText: 2019-2022 Connor McLaughlin <stenzek@gmail.com>
|
|
|
|
// SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0)
|
|
|
|
|
2021-02-26 14:44:53 +00:00
|
|
|
#include "achievementsettingswidget.h"
|
|
|
|
#include "achievementlogindialog.h"
|
2021-03-03 09:14:01 +00:00
|
|
|
#include "mainwindow.h"
|
2021-02-26 14:44:53 +00:00
|
|
|
#include "qtutils.h"
|
|
|
|
#include "settingsdialog.h"
|
|
|
|
#include "settingwidgetbinder.h"
|
2023-08-13 06:28:28 +00:00
|
|
|
|
2023-08-23 12:06:48 +00:00
|
|
|
#include "core/achievements.h"
|
2023-08-13 06:28:28 +00:00
|
|
|
#include "core/system.h"
|
|
|
|
|
|
|
|
#include "common/string_util.h"
|
|
|
|
|
2021-02-26 14:44:53 +00:00
|
|
|
#include <QtCore/QDateTime>
|
|
|
|
#include <QtWidgets/QMessageBox>
|
|
|
|
|
2022-07-11 13:03:29 +00:00
|
|
|
AchievementSettingsWidget::AchievementSettingsWidget(SettingsDialog* dialog, QWidget* parent)
|
|
|
|
: QWidget(parent), m_dialog(dialog)
|
2021-02-26 14:44:53 +00:00
|
|
|
{
|
2022-07-11 13:03:29 +00:00
|
|
|
SettingsInterface* sif = dialog->getSettingsInterface();
|
|
|
|
|
2021-02-26 14:44:53 +00:00
|
|
|
m_ui.setupUi(this);
|
|
|
|
|
2022-08-22 10:01:04 +00:00
|
|
|
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.enable, "Cheevos", "Enabled", false);
|
2023-09-07 10:13:48 +00:00
|
|
|
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.hardcoreMode, "Cheevos", "ChallengeMode", false);
|
|
|
|
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.achievementNotifications, "Cheevos", "Notifications", true);
|
|
|
|
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.leaderboardNotifications, "Cheevos",
|
|
|
|
"LeaderboardNotifications", true);
|
|
|
|
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.soundEffects, "Cheevos", "SoundEffects", true);
|
|
|
|
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.overlays, "Cheevos", "Overlays", true);
|
|
|
|
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.encoreMode, "Cheevos", "EncoreMode", false);
|
|
|
|
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.spectatorMode, "Cheevos", "SpectatorMode", false);
|
|
|
|
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.unofficialAchievements, "Cheevos", "UnofficialTestMode",
|
|
|
|
false);
|
2022-07-11 13:03:29 +00:00
|
|
|
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.useFirstDiscFromPlaylist, "Cheevos",
|
2021-02-26 14:44:53 +00:00
|
|
|
"UseFirstDiscFromPlaylist", true);
|
2023-09-07 10:13:48 +00:00
|
|
|
SettingWidgetBinder::BindWidgetToFloatSetting(sif, m_ui.achievementNotificationsDuration, "Cheevos",
|
|
|
|
"NotificationsDuration",
|
|
|
|
Settings::DEFAULT_ACHIEVEMENT_NOTIFICATION_TIME);
|
|
|
|
SettingWidgetBinder::BindWidgetToFloatSetting(sif, m_ui.leaderboardNotificationsDuration, "Cheevos",
|
|
|
|
"LeaderboardsDuration",
|
|
|
|
Settings::DEFAULT_LEADERBOARD_NOTIFICATION_TIME);
|
2021-02-26 14:44:53 +00:00
|
|
|
|
|
|
|
dialog->registerWidgetHelp(m_ui.enable, tr("Enable Achievements"), tr("Unchecked"),
|
|
|
|
tr("When enabled and logged in, DuckStation will scan for achievements on startup."));
|
2023-09-07 10:13:48 +00:00
|
|
|
dialog->registerWidgetHelp(m_ui.hardcoreMode, tr("Enable Hardcore Mode"), tr("Unchecked"),
|
|
|
|
tr("\"Challenge\" mode for achievements, including leaderboard tracking. Disables save "
|
|
|
|
"state, cheats, and slowdown functions."));
|
|
|
|
dialog->registerWidgetHelp(m_ui.achievementNotifications, tr("Show Achievement Notifications"), tr("Checked"),
|
|
|
|
tr("Displays popup messages on events such as achievement unlocks and game completion."));
|
|
|
|
dialog->registerWidgetHelp(
|
|
|
|
m_ui.leaderboardNotifications, tr("Show Leaderboard Notifications"), tr("Checked"),
|
|
|
|
tr("Displays popup messages when starting, submitting, or failing a leaderboard challenge."));
|
|
|
|
dialog->registerWidgetHelp(
|
|
|
|
m_ui.soundEffects, tr("Enable Sound Effects"), tr("Checked"),
|
|
|
|
tr("Plays sound effects for events such as achievement unlocks and leaderboard submissions."));
|
|
|
|
dialog->registerWidgetHelp(
|
|
|
|
m_ui.overlays, tr("Enable In-Game Overlays"), tr("Checked"),
|
|
|
|
tr("Shows icons in the lower-right corner of the screen when a challenge/primed achievement is active."));
|
|
|
|
dialog->registerWidgetHelp(m_ui.encoreMode, tr("Enable Encore Mode"), tr("Unchecked"),
|
|
|
|
tr("When enabled, each session will behave as if no achievements have been unlocked."));
|
|
|
|
dialog->registerWidgetHelp(m_ui.spectatorMode, tr("Enable Spectator Mode"), tr("Unchecked"),
|
2021-02-26 14:44:53 +00:00
|
|
|
tr("When enabled, DuckStation will assume all achievements are locked and not send any "
|
|
|
|
"unlock notifications to the server."));
|
2021-06-09 18:10:18 +00:00
|
|
|
dialog->registerWidgetHelp(
|
2023-09-07 10:13:48 +00:00
|
|
|
m_ui.unofficialAchievements, tr("Test Unofficial Achievements"), tr("Unchecked"),
|
2021-06-09 18:10:18 +00:00
|
|
|
tr("When enabled, DuckStation will list achievements from unofficial sets. Please note that these achievements are "
|
|
|
|
"not tracked by RetroAchievements, so they unlock every time."));
|
2021-02-26 14:44:53 +00:00
|
|
|
dialog->registerWidgetHelp(
|
|
|
|
m_ui.useFirstDiscFromPlaylist, tr("Use First Disc From Playlist"), tr("Unchecked"),
|
|
|
|
tr(
|
|
|
|
"When enabled, the first disc in a playlist will be used for achievements, regardless of which disc is active."));
|
|
|
|
|
2022-07-11 13:03:29 +00:00
|
|
|
connect(m_ui.enable, &QCheckBox::stateChanged, this, &AchievementSettingsWidget::updateEnableState);
|
2023-09-07 10:13:48 +00:00
|
|
|
connect(m_ui.hardcoreMode, &QCheckBox::stateChanged, this, &AchievementSettingsWidget::updateEnableState);
|
|
|
|
connect(m_ui.hardcoreMode, &QCheckBox::stateChanged, this, &AchievementSettingsWidget::onHardcoreModeStateChanged);
|
|
|
|
connect(m_ui.achievementNotifications, &QCheckBox::stateChanged, this, &AchievementSettingsWidget::updateEnableState);
|
|
|
|
connect(m_ui.leaderboardNotifications, &QCheckBox::stateChanged, this, &AchievementSettingsWidget::updateEnableState);
|
|
|
|
connect(m_ui.achievementNotificationsDuration, &QSlider::valueChanged, this,
|
|
|
|
&AchievementSettingsWidget::onAchievementsNotificationDurationSliderChanged);
|
|
|
|
connect(m_ui.leaderboardNotificationsDuration, &QSlider::valueChanged, this,
|
|
|
|
&AchievementSettingsWidget::onLeaderboardsNotificationDurationSliderChanged);
|
2021-02-26 14:44:53 +00:00
|
|
|
|
2022-07-11 13:03:29 +00:00
|
|
|
if (!m_dialog->isPerGameSettings())
|
|
|
|
{
|
|
|
|
connect(m_ui.loginButton, &QPushButton::clicked, this, &AchievementSettingsWidget::onLoginLogoutPressed);
|
|
|
|
connect(m_ui.viewProfile, &QPushButton::clicked, this, &AchievementSettingsWidget::onViewProfilePressed);
|
|
|
|
connect(g_emu_thread, &EmuThread::achievementsRefreshed, this, &AchievementSettingsWidget::onAchievementsRefreshed);
|
|
|
|
updateLoginState();
|
|
|
|
|
|
|
|
// force a refresh of game info
|
|
|
|
Host::RunOnCPUThread(Host::OnAchievementsRefreshed);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// remove login and game info, not relevant for per-game
|
|
|
|
m_ui.verticalLayout->removeWidget(m_ui.gameInfoBox);
|
|
|
|
m_ui.gameInfoBox->deleteLater();
|
|
|
|
m_ui.gameInfoBox = nullptr;
|
|
|
|
m_ui.verticalLayout->removeWidget(m_ui.loginBox);
|
|
|
|
m_ui.loginBox->deleteLater();
|
|
|
|
m_ui.loginBox = nullptr;
|
|
|
|
}
|
2021-02-26 14:44:53 +00:00
|
|
|
|
2022-07-11 13:03:29 +00:00
|
|
|
updateEnableState();
|
2023-09-07 10:13:48 +00:00
|
|
|
onAchievementsNotificationDurationSliderChanged();
|
|
|
|
onLeaderboardsNotificationDurationSliderChanged();
|
2021-02-26 14:44:53 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
AchievementSettingsWidget::~AchievementSettingsWidget() = default;
|
|
|
|
|
|
|
|
void AchievementSettingsWidget::updateEnableState()
|
|
|
|
{
|
2022-07-11 13:03:29 +00:00
|
|
|
const bool enabled = m_dialog->getEffectiveBoolValue("Cheevos", "Enabled", false);
|
2023-09-07 10:13:48 +00:00
|
|
|
const bool notifications = enabled && m_dialog->getEffectiveBoolValue("Cheevos", "Notifications", true);
|
|
|
|
const bool lb_notifications = enabled && m_dialog->getEffectiveBoolValue("Cheevos", "LeaderboardNotifications", true);
|
|
|
|
m_ui.hardcoreMode->setEnabled(enabled);
|
|
|
|
m_ui.achievementNotifications->setEnabled(enabled);
|
|
|
|
m_ui.leaderboardNotifications->setEnabled(enabled);
|
|
|
|
m_ui.achievementNotificationsDuration->setEnabled(notifications);
|
|
|
|
m_ui.achievementNotificationsDurationLabel->setEnabled(notifications);
|
|
|
|
m_ui.leaderboardNotificationsDuration->setEnabled(lb_notifications);
|
|
|
|
m_ui.leaderboardNotificationsDurationLabel->setEnabled(lb_notifications);
|
2023-01-28 12:35:24 +00:00
|
|
|
m_ui.soundEffects->setEnabled(enabled);
|
2023-09-07 10:13:48 +00:00
|
|
|
m_ui.overlays->setEnabled(enabled);
|
|
|
|
m_ui.encoreMode->setEnabled(enabled);
|
|
|
|
m_ui.spectatorMode->setEnabled(enabled);
|
|
|
|
m_ui.unofficialAchievements->setEnabled(enabled);
|
|
|
|
m_ui.useFirstDiscFromPlaylist->setEnabled(enabled);
|
2022-09-21 13:45:24 +00:00
|
|
|
}
|
|
|
|
|
2023-09-07 10:13:48 +00:00
|
|
|
void AchievementSettingsWidget::onHardcoreModeStateChanged()
|
2022-09-21 13:45:24 +00:00
|
|
|
{
|
|
|
|
if (!QtHost::IsSystemValid())
|
|
|
|
return;
|
|
|
|
|
|
|
|
const bool enabled = m_dialog->getEffectiveBoolValue("Cheevos", "Enabled", false);
|
|
|
|
const bool challenge = m_dialog->getEffectiveBoolValue("Cheevos", "ChallengeMode", false);
|
|
|
|
if (!enabled || !challenge)
|
|
|
|
return;
|
|
|
|
|
|
|
|
// don't bother prompting if the game doesn't have achievements
|
|
|
|
auto lock = Achievements::GetLock();
|
|
|
|
if (!Achievements::HasActiveGame())
|
|
|
|
return;
|
|
|
|
|
|
|
|
if (QMessageBox::question(
|
|
|
|
QtUtils::GetRootWidget(this), tr("Reset System"),
|
|
|
|
tr("Hardcore mode will not be enabled until the system is reset. Do you want to reset the system now?")) !=
|
|
|
|
QMessageBox::Yes)
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
g_emu_thread->resetSystem();
|
2021-02-26 14:44:53 +00:00
|
|
|
}
|
|
|
|
|
2023-09-07 10:13:48 +00:00
|
|
|
void AchievementSettingsWidget::onAchievementsNotificationDurationSliderChanged()
|
|
|
|
{
|
2023-09-18 12:29:47 +00:00
|
|
|
const int duration =
|
|
|
|
m_dialog->getEffectiveIntValue("Cheevos", "NotificationsDuration", Settings::DEFAULT_ACHIEVEMENT_NOTIFICATION_TIME);
|
|
|
|
m_ui.achievementNotificationsDurationLabel->setText(tr("%n seconds", nullptr, duration));
|
2023-09-07 10:13:48 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void AchievementSettingsWidget::onLeaderboardsNotificationDurationSliderChanged()
|
|
|
|
{
|
2023-09-18 12:29:47 +00:00
|
|
|
const int duration =
|
|
|
|
m_dialog->getEffectiveIntValue("Cheevos", "LeaderboardsDuration", Settings::DEFAULT_LEADERBOARD_NOTIFICATION_TIME);
|
|
|
|
m_ui.leaderboardNotificationsDurationLabel->setText(tr("%n seconds", nullptr, duration));
|
2023-09-07 10:13:48 +00:00
|
|
|
}
|
|
|
|
|
2021-02-26 14:44:53 +00:00
|
|
|
void AchievementSettingsWidget::updateLoginState()
|
|
|
|
{
|
2022-07-11 13:03:29 +00:00
|
|
|
const std::string username(Host::GetBaseStringSettingValue("Cheevos", "Username"));
|
2021-02-26 14:44:53 +00:00
|
|
|
const bool logged_in = !username.empty();
|
|
|
|
|
|
|
|
if (logged_in)
|
|
|
|
{
|
|
|
|
const u64 login_unix_timestamp =
|
2022-07-11 13:03:29 +00:00
|
|
|
StringUtil::FromChars<u64>(Host::GetBaseStringSettingValue("Cheevos", "LoginTimestamp", "0")).value_or(0);
|
2021-02-26 14:44:53 +00:00
|
|
|
const QDateTime login_timestamp(QDateTime::fromSecsSinceEpoch(static_cast<qint64>(login_unix_timestamp)));
|
|
|
|
m_ui.loginStatus->setText(tr("Username: %1\nLogin token generated on %2.")
|
|
|
|
.arg(QString::fromStdString(username))
|
|
|
|
.arg(login_timestamp.toString(Qt::TextDate)));
|
|
|
|
m_ui.loginButton->setText(tr("Logout"));
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
m_ui.loginStatus->setText(tr("Not Logged In."));
|
|
|
|
m_ui.loginButton->setText(tr("Login..."));
|
|
|
|
}
|
2021-03-16 14:41:22 +00:00
|
|
|
|
|
|
|
m_ui.viewProfile->setEnabled(logged_in);
|
2021-02-26 14:44:53 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void AchievementSettingsWidget::onLoginLogoutPressed()
|
|
|
|
{
|
2022-07-11 13:03:29 +00:00
|
|
|
if (!Host::GetBaseStringSettingValue("Cheevos", "Username").empty())
|
2021-02-26 14:44:53 +00:00
|
|
|
{
|
2022-07-11 13:03:29 +00:00
|
|
|
Host::RunOnCPUThread([]() { Achievements::Logout(); }, true);
|
2021-02-26 14:44:53 +00:00
|
|
|
updateLoginState();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2023-09-07 10:13:48 +00:00
|
|
|
AchievementLoginDialog login(this, Achievements::LoginRequestReason::UserInitiated);
|
2021-02-26 14:44:53 +00:00
|
|
|
int res = login.exec();
|
|
|
|
if (res != 0)
|
|
|
|
return;
|
|
|
|
|
|
|
|
updateLoginState();
|
|
|
|
}
|
|
|
|
|
|
|
|
void AchievementSettingsWidget::onViewProfilePressed()
|
|
|
|
{
|
2022-07-11 13:03:29 +00:00
|
|
|
const std::string username(Host::GetBaseStringSettingValue("Cheevos", "Username"));
|
2021-03-03 09:14:01 +00:00
|
|
|
if (username.empty())
|
2021-02-26 14:44:53 +00:00
|
|
|
return;
|
|
|
|
|
2021-03-03 09:14:01 +00:00
|
|
|
const QByteArray encoded_username(QUrl::toPercentEncoding(QString::fromStdString(username)));
|
2021-02-26 14:44:53 +00:00
|
|
|
QtUtils::OpenURL(
|
|
|
|
QtUtils::GetRootWidget(this),
|
|
|
|
QUrl(QStringLiteral("https://retroachievements.org/user/%1").arg(QString::fromUtf8(encoded_username))));
|
|
|
|
}
|
|
|
|
|
2023-09-07 10:13:48 +00:00
|
|
|
void AchievementSettingsWidget::onAchievementsRefreshed(quint32 id, const QString& game_info_string)
|
2021-02-26 14:44:53 +00:00
|
|
|
{
|
|
|
|
m_ui.gameInfo->setText(game_info_string);
|
|
|
|
}
|