mirror of https://github.com/PCSX2/pcsx2.git
Achievements: Switch to rc_client
This commit is contained in:
parent
25a3ea98bc
commit
10ec91065e
|
@ -428,6 +428,8 @@ void MainWindow::connectVMThreadSignals(EmuThread* thread)
|
|||
connect(thread, &EmuThread::onCaptureStarted, this, &MainWindow::onCaptureStarted);
|
||||
connect(thread, &EmuThread::onCaptureStopped, this, &MainWindow::onCaptureStopped);
|
||||
connect(thread, &EmuThread::onAchievementsLoginRequested, this, &MainWindow::onAchievementsLoginRequested);
|
||||
connect(thread, &EmuThread::onAchievementsLoginSucceeded, this, &MainWindow::onAchievementsLoginSucceeded);
|
||||
connect(thread, &EmuThread::onAchievementsHardcoreModeChanged, this, &MainWindow::onAchievementsHardcoreModeChanged);
|
||||
|
||||
connect(m_ui.actionReset, &QAction::triggered, thread, &EmuThread::resetVM);
|
||||
connect(m_ui.actionPause, &QAction::toggled, thread, &EmuThread::setVMPaused);
|
||||
|
@ -637,10 +639,32 @@ void MainWindow::onAchievementsLoginRequested(Achievements::LoginRequestReason r
|
|||
{
|
||||
auto lock = pauseAndLockVM();
|
||||
|
||||
AchievementLoginDialog dlg(this, reason);
|
||||
AchievementLoginDialog dlg(lock.getDialogParent(), reason);
|
||||
dlg.exec();
|
||||
}
|
||||
|
||||
void MainWindow::onAchievementsLoginSucceeded(const QString& display_name, quint32 points, quint32 sc_points, quint32 unread_messages)
|
||||
{
|
||||
const QString message =
|
||||
tr("RA: Logged in as %1 (%2, %3 softcore). %4 unread messages.").arg(display_name).arg(points).arg(sc_points).arg(unread_messages);
|
||||
m_ui.statusBar->showMessage(message);
|
||||
}
|
||||
|
||||
void MainWindow::onAchievementsHardcoreModeChanged(bool enabled)
|
||||
{
|
||||
// disable debugger while hardcore mode is active
|
||||
m_ui.actionDebugger->setDisabled(enabled);
|
||||
if (enabled)
|
||||
{
|
||||
if (m_debugger_window)
|
||||
{
|
||||
m_debugger_window->close();
|
||||
m_debugger_window->deleteLater();
|
||||
m_debugger_window = nullptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::onSettingsTriggeredFromToolbar()
|
||||
{
|
||||
if (s_vm_valid)
|
||||
|
|
|
@ -196,6 +196,8 @@ private Q_SLOTS:
|
|||
void onCaptureStopped();
|
||||
|
||||
void onAchievementsLoginRequested(Achievements::LoginRequestReason reason);
|
||||
void onAchievementsLoginSucceeded(const QString& display_name, quint32 points, quint32 sc_points, quint32 unread_messages);
|
||||
void onAchievementsHardcoreModeChanged(bool enabled);
|
||||
|
||||
protected:
|
||||
void showEvent(QShowEvent* event) override;
|
||||
|
|
|
@ -491,7 +491,7 @@ void EmuThread::stopBackgroundControllerPollTimer()
|
|||
|
||||
void EmuThread::doBackgroundControllerPoll()
|
||||
{
|
||||
InputManager::PollSources();
|
||||
VMManager::IdlePollUpdate();
|
||||
}
|
||||
|
||||
void EmuThread::toggleFullscreen()
|
||||
|
@ -1115,31 +1115,29 @@ void Host::OnAchievementsLoginRequested(Achievements::LoginRequestReason reason)
|
|||
emit g_emu_thread->onAchievementsLoginRequested(reason);
|
||||
}
|
||||
|
||||
void Host::OnAchievementsLoginSuccess(const char* username, u32 points, u32 sc_points, u32 unread_messages)
|
||||
{
|
||||
emit g_emu_thread->onAchievementsLoginSucceeded(QString::fromUtf8(username), points, sc_points, unread_messages);
|
||||
}
|
||||
|
||||
void Host::OnAchievementsRefreshed()
|
||||
{
|
||||
u32 game_id = 0;
|
||||
u32 achievement_count = 0;
|
||||
u32 max_points = 0;
|
||||
|
||||
QString game_info;
|
||||
|
||||
if (Achievements::HasActiveGame())
|
||||
{
|
||||
game_id = Achievements::GetGameID();
|
||||
achievement_count = Achievements::GetAchievementCount();
|
||||
max_points = Achievements::GetMaximumPointsForGame();
|
||||
|
||||
game_info = qApp->translate("EmuThread", "Game ID: %1\n"
|
||||
"Game Title: %2\n"
|
||||
"Achievements: %5 (%6)\n\n")
|
||||
.arg(game_id)
|
||||
game_info = qApp
|
||||
->translate("EmuThread", "Game: %1 (%2)\n")
|
||||
.arg(QString::fromStdString(Achievements::GetGameTitle()))
|
||||
.arg(achievement_count)
|
||||
.arg(qApp->translate("EmuThread", "%n points", "", max_points));
|
||||
.arg(game_id);
|
||||
|
||||
const std::string rich_presence_string(Achievements::GetRichPresenceString());
|
||||
const std::string& rich_presence_string = Achievements::GetRichPresenceString();
|
||||
if (!rich_presence_string.empty())
|
||||
game_info.append(QString::fromStdString(rich_presence_string));
|
||||
game_info.append(QString::fromStdString(StringUtil::Ellipsise(rich_presence_string, 128)));
|
||||
else
|
||||
game_info.append(qApp->translate("EmuThread", "Rich presence inactive or unsupported."));
|
||||
}
|
||||
|
@ -1148,7 +1146,12 @@ void Host::OnAchievementsRefreshed()
|
|||
game_info = qApp->translate("EmuThread", "Game not loaded or no RetroAchievements available.");
|
||||
}
|
||||
|
||||
emit g_emu_thread->onAchievementsRefreshed(game_id, game_info, achievement_count, max_points);
|
||||
emit g_emu_thread->onAchievementsRefreshed(game_id, game_info);
|
||||
}
|
||||
|
||||
void Host::OnAchievementsHardcoreModeChanged(bool enabled)
|
||||
{
|
||||
emit g_emu_thread->onAchievementsHardcoreModeChanged(enabled);
|
||||
}
|
||||
|
||||
void Host::VSyncOnCPUThread()
|
||||
|
|
|
@ -167,8 +167,14 @@ Q_SIGNALS:
|
|||
/// Called when achievements login is requested.
|
||||
void onAchievementsLoginRequested(Achievements::LoginRequestReason reason);
|
||||
|
||||
/// Called when achievements login succeeds. Also happens on startup.
|
||||
void onAchievementsLoginSucceeded(const QString& display_name, quint32 points, quint32 sc_points, quint32 unread_messages);
|
||||
|
||||
/// Called when achievements are reloaded/refreshed (e.g. game change, login, option change).
|
||||
void onAchievementsRefreshed(quint32 id, const QString& game_info_string, quint32 total, quint32 points);
|
||||
void onAchievementsRefreshed(quint32 id, const QString& game_info_string);
|
||||
|
||||
/// Called when hardcore mode is enabled or disabled.
|
||||
void onAchievementsHardcoreModeChanged(bool enabled);
|
||||
|
||||
/// Called when video capture starts/stops.
|
||||
void onCaptureStarted(const QString& filename);
|
||||
|
|
|
@ -20,10 +20,13 @@
|
|||
|
||||
#include "pcsx2/Achievements.h"
|
||||
|
||||
#include "common/Error.h"
|
||||
|
||||
#include <QtWidgets/QMessageBox>
|
||||
|
||||
AchievementLoginDialog::AchievementLoginDialog(QWidget* parent, Achievements::LoginRequestReason reason)
|
||||
: QDialog(parent)
|
||||
, m_reason(reason)
|
||||
{
|
||||
m_ui.setupUi(this);
|
||||
m_ui.loginIcon->setPixmap(QIcon::fromTheme("login-box-line").pixmap(32));
|
||||
|
@ -55,22 +58,34 @@ void AchievementLoginDialog::loginClicked()
|
|||
enableUI(false);
|
||||
|
||||
Host::RunOnCPUThread([this, username = std::move(username), password = std::move(password)]() {
|
||||
const bool result = Achievements::Login(username.c_str(), password.c_str());
|
||||
QMetaObject::invokeMethod(this, "processLoginResult", Qt::QueuedConnection, Q_ARG(bool, result));
|
||||
Error error;
|
||||
const bool result = Achievements::Login(username.c_str(), password.c_str(), &error);
|
||||
const QString message = QString::fromStdString(error.GetDescription());
|
||||
QMetaObject::invokeMethod(this, "processLoginResult", Qt::QueuedConnection, Q_ARG(bool, result), Q_ARG(const QString&, message));
|
||||
});
|
||||
}
|
||||
|
||||
void AchievementLoginDialog::cancelClicked()
|
||||
{
|
||||
// Disable hardcore mode if we cancelled reauthentication.
|
||||
if (m_reason == Achievements::LoginRequestReason::TokenInvalid && QtHost::IsVMValid())
|
||||
{
|
||||
Host::RunOnCPUThread([]() {
|
||||
if (VMManager::HasValidVM() && !Achievements::HasActiveGame())
|
||||
Achievements::DisableHardcoreMode();
|
||||
});
|
||||
}
|
||||
|
||||
done(1);
|
||||
}
|
||||
|
||||
void AchievementLoginDialog::processLoginResult(bool result)
|
||||
void AchievementLoginDialog::processLoginResult(bool result, const QString& message)
|
||||
{
|
||||
if (!result)
|
||||
{
|
||||
QMessageBox::critical(this, tr("Login Error"),
|
||||
tr("Login failed. Please check your username and password, and try again."));
|
||||
QMessageBox::critical(
|
||||
this, tr("Login Error"),
|
||||
tr("Login failed.\nError: %1\n\nPlease check your username and password, and try again.").arg(message));
|
||||
m_ui.status->setText(tr("Login failed."));
|
||||
enableUI(true);
|
||||
return;
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/* PCSX2 - PS2 Emulator for PCs
|
||||
* Copyright (C) 2002-2022 PCSX2 Dev Team
|
||||
* Copyright (C) 2002-2023 PCSX2 Dev Team
|
||||
*
|
||||
* PCSX2 is free software: you can redistribute it and/or modify it under the terms
|
||||
* of the GNU Lesser General Public License as published by the Free Software Found-
|
||||
|
@ -34,7 +34,7 @@ public:
|
|||
private Q_SLOTS:
|
||||
void loginClicked();
|
||||
void cancelClicked();
|
||||
void processLoginResult(bool result);
|
||||
void processLoginResult(bool result, const QString& message);
|
||||
|
||||
private:
|
||||
void connectUi();
|
||||
|
|
|
@ -30,63 +30,43 @@
|
|||
#include <QtCore/QDateTime>
|
||||
#include <QtWidgets/QMessageBox>
|
||||
|
||||
static constexpr s32 DEFAULT_NOTIFICATIONS_DURATION = 5;
|
||||
|
||||
AchievementSettingsWidget::AchievementSettingsWidget(SettingsDialog* dialog, QWidget* parent)
|
||||
: QWidget(parent)
|
||||
, m_dialog(dialog)
|
||||
{
|
||||
m_ui.setupUi(this);
|
||||
|
||||
SettingsInterface* sif = dialog->getSettingsInterface();
|
||||
|
||||
m_ui.setupUi(this);
|
||||
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.enable, "Achievements", "Enabled", false);
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.richPresence, "Achievements", "RichPresence", true);
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.challengeMode, "Achievements", "ChallengeMode", false);
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.leaderboards, "Achievements", "Leaderboards", true);
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.testMode, "Achievements", "TestMode", false);
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.unofficialTestMode, "Achievements", "UnofficialTestMode", false);
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.notifications, "Achievements", "Notifications", true);
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.hardcoreMode, "Achievements", "ChallengeMode", false);
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.achievementNotifications, "Achievements", "Notifications", true);
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.leaderboardNotifications, "Achievements", "LeaderboardNotifications", true);
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.soundEffects, "Achievements", "SoundEffects", true);
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.primedIndicators, "Achievements", "PrimedIndicators", true);
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.overlays, "Achievements", "Overlays", true);
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.encoreMode, "Achievements", "EncoreMode", false);
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.spectatorMode, "Achievements", "SpectatorMode", false);
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.unofficialAchievements, "Achievements", "UnofficialTestMode",false);
|
||||
SettingWidgetBinder::BindWidgetToIntSetting(sif, m_ui.achievementNotificationsDuration, "Achievements", "NotificationsDuration", Pcsx2Config::AchievementsOptions::DEFAULT_NOTIFICATION_DURATION);
|
||||
SettingWidgetBinder::BindWidgetToIntSetting(sif, m_ui.leaderboardNotificationsDuration, "Achievements", "LeaderboardsDuration", Pcsx2Config::AchievementsOptions::DEFAULT_LEADERBOARD_DURATION);
|
||||
|
||||
SettingWidgetBinder::BindSliderToIntSetting(sif, m_ui.notifications_duration, m_ui.notifications_duration_seconds,
|
||||
tr(" seconds"), "Achievements", "NotificationsDuration", DEFAULT_NOTIFICATIONS_DURATION);
|
||||
|
||||
dialog->registerWidgetHelp(m_ui.enable, tr("Enable Achievements"), tr("Unchecked"),
|
||||
tr("When enabled and logged in, PCSX2 will scan for achievements on game load."));
|
||||
dialog->registerWidgetHelp(m_ui.testMode, tr("Enable Test Mode"), tr("Unchecked"),
|
||||
tr("When enabled, PCSX2 will assume all achievements are locked and not send any "
|
||||
"unlock notifications to the server."));
|
||||
dialog->registerWidgetHelp(m_ui.unofficialTestMode, tr("Test Unofficial Achievements"), tr("Unchecked"),
|
||||
tr("When enabled, PCSX2 will list achievements from unofficial sets. Please note that these achievements are "
|
||||
"not tracked by RetroAchievements, so they unlock every time."));
|
||||
dialog->registerWidgetHelp(m_ui.richPresence, tr("Enable RA's Rich Presence"), tr("Unchecked"),
|
||||
tr("When enabled, rich presence information will be collected and sent to the RetroAchievements servers where supported."));
|
||||
dialog->registerWidgetHelp(m_ui.challengeMode, 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.leaderboards, tr("Enable Leaderboards"), tr("Checked"),
|
||||
tr("Enables tracking and submission of leaderboards in supported games. If leaderboards are disabled, you will still "
|
||||
"be able to view the leaderboard and scores, but no scores will be uploaded."));
|
||||
dialog->registerWidgetHelp(m_ui.notifications, tr("Show Notifications"), tr("Checked"),
|
||||
tr("Displays popup messages on events such as achievement unlocks and leaderboard submissions."));
|
||||
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.primedIndicators, tr("Show Challenge Indicators"), tr("Checked"),
|
||||
tr("Shows icons in the lower-right corner of the screen when a challenge/primed achievement is active."));
|
||||
|
||||
dialog->registerWidgetHelp(m_ui.notifications_duration, tr("Notification Duration"),
|
||||
tr("5 seconds"), tr("The duration, in seconds, an achievement popup notification will remain on screen."));
|
||||
dialog->registerWidgetHelp(m_ui.notifications_duration_label, tr("Notification Duration"),
|
||||
tr("5 seconds"), tr("The duration, in seconds, an achievement popup notification will remain on screen."));
|
||||
dialog->registerWidgetHelp(m_ui.notifications_duration_seconds, tr("Notification Duration"),
|
||||
tr("5 seconds"), tr("The duration, in seconds, an achievement popup notification will remain on screen."));
|
||||
dialog->registerWidgetHelp(m_ui.enable, tr("Enable Achievements"), tr("Unchecked"), tr("When enabled and logged in, PCSX2 will scan for achievements on startup."));
|
||||
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"), tr("When enabled, PCSX2 will assume all achievements are locked and not send any unlock notifications to the server."));
|
||||
dialog->registerWidgetHelp(m_ui.unofficialAchievements, tr("Test Unofficial Achievements"), tr("Unchecked"), tr("When enabled, PCSX2 will list achievements from unofficial sets. Please note that these achievements are not tracked by RetroAchievements, so they unlock every time."));
|
||||
|
||||
connect(m_ui.enable, &QCheckBox::stateChanged, this, &AchievementSettingsWidget::updateEnableState);
|
||||
connect(m_ui.notifications, &QCheckBox::stateChanged, this, &AchievementSettingsWidget::updateEnableState);
|
||||
connect(m_ui.challengeMode, &QCheckBox::stateChanged, this, &AchievementSettingsWidget::updateEnableState);
|
||||
connect(m_ui.challengeMode, &QCheckBox::stateChanged, this, &AchievementSettingsWidget::onChallengeModeStateChanged);
|
||||
connect(m_ui.notifications_duration, &QSlider::valueChanged, this, &AchievementSettingsWidget::onNotificationsDurationChanged);
|
||||
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);
|
||||
|
||||
if (!m_dialog->isPerGameSettings())
|
||||
{
|
||||
|
@ -110,6 +90,8 @@ AchievementSettingsWidget::AchievementSettingsWidget(SettingsDialog* dialog, QWi
|
|||
}
|
||||
|
||||
updateEnableState();
|
||||
onAchievementsNotificationDurationSliderChanged();
|
||||
onLeaderboardsNotificationDurationSliderChanged();
|
||||
}
|
||||
|
||||
AchievementSettingsWidget::~AchievementSettingsWidget() = default;
|
||||
|
@ -117,24 +99,23 @@ AchievementSettingsWidget::~AchievementSettingsWidget() = default;
|
|||
void AchievementSettingsWidget::updateEnableState()
|
||||
{
|
||||
const bool enabled = m_dialog->getEffectiveBoolValue("Achievements", "Enabled", false);
|
||||
const bool challenge = m_dialog->getEffectiveBoolValue("Achievements", "ChallengeMode", false);
|
||||
const bool notifications = m_dialog->getEffectiveBoolValue("Achievements", "Notifications", true);
|
||||
|
||||
m_ui.testMode->setEnabled(enabled);
|
||||
m_ui.unofficialTestMode->setEnabled(enabled);
|
||||
m_ui.richPresence->setEnabled(enabled);
|
||||
m_ui.challengeMode->setEnabled(enabled);
|
||||
m_ui.leaderboards->setEnabled(enabled && challenge);
|
||||
m_ui.notifications->setEnabled(enabled);
|
||||
const bool notifications = enabled && m_dialog->getEffectiveBoolValue("Achievements", "Notifications", true);
|
||||
const bool lb_notifications = enabled && m_dialog->getEffectiveBoolValue("Achievements", "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);
|
||||
m_ui.soundEffects->setEnabled(enabled);
|
||||
m_ui.primedIndicators->setEnabled(enabled);
|
||||
|
||||
m_ui.notifications_duration->setEnabled(enabled && notifications);
|
||||
m_ui.notifications_duration_label->setEnabled(enabled && notifications);
|
||||
m_ui.notifications_duration_seconds->setEnabled(enabled && notifications);
|
||||
m_ui.overlays->setEnabled(enabled);
|
||||
m_ui.encoreMode->setEnabled(enabled);
|
||||
m_ui.spectatorMode->setEnabled(enabled);
|
||||
m_ui.unofficialAchievements->setEnabled(enabled);
|
||||
}
|
||||
|
||||
void AchievementSettingsWidget::onChallengeModeStateChanged()
|
||||
void AchievementSettingsWidget::onHardcoreModeStateChanged()
|
||||
{
|
||||
if (!QtHost::IsVMValid())
|
||||
return;
|
||||
|
@ -149,8 +130,10 @@ void AchievementSettingsWidget::onChallengeModeStateChanged()
|
|||
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)
|
||||
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;
|
||||
}
|
||||
|
@ -158,6 +141,20 @@ void AchievementSettingsWidget::onChallengeModeStateChanged()
|
|||
g_emu_thread->resetVM();
|
||||
}
|
||||
|
||||
void AchievementSettingsWidget::onAchievementsNotificationDurationSliderChanged()
|
||||
{
|
||||
const float duration = m_dialog->getEffectiveFloatValue("Achievements", "NotificationsDuration",
|
||||
Pcsx2Config::AchievementsOptions::DEFAULT_NOTIFICATION_DURATION);
|
||||
m_ui.achievementNotificationsDurationLabel->setText(tr("%n seconds", nullptr, static_cast<int>(duration)));
|
||||
}
|
||||
|
||||
void AchievementSettingsWidget::onLeaderboardsNotificationDurationSliderChanged()
|
||||
{
|
||||
const float duration = m_dialog->getEffectiveFloatValue("Achievements", "LeaderboardsDuration",
|
||||
Pcsx2Config::AchievementsOptions::DEFAULT_LEADERBOARD_DURATION);
|
||||
m_ui.leaderboardNotificationsDurationLabel->setText(tr("%n seconds", nullptr, static_cast<int>(duration)));
|
||||
}
|
||||
|
||||
void AchievementSettingsWidget::updateLoginState()
|
||||
{
|
||||
const std::string username(Host::GetBaseStringSettingValue("Achievements", "Username"));
|
||||
|
@ -168,7 +165,6 @@ void AchievementSettingsWidget::updateLoginState()
|
|||
const u64 login_unix_timestamp =
|
||||
StringUtil::FromChars<u64>(Host::GetBaseStringSettingValue("Achievements", "LoginTimestamp", "0")).value_or(0);
|
||||
const QDateTime login_timestamp(QDateTime::fromSecsSinceEpoch(static_cast<qint64>(login_unix_timestamp)));
|
||||
//: Variable %1 is an username, variable %2 is a timestamp.
|
||||
m_ui.loginStatus->setText(tr("Username: %1\nLogin token generated on %2.")
|
||||
.arg(QString::fromStdString(username))
|
||||
.arg(login_timestamp.toString(Qt::TextDate)));
|
||||
|
@ -187,7 +183,7 @@ void AchievementSettingsWidget::onLoginLogoutPressed()
|
|||
{
|
||||
if (!Host::GetBaseStringSettingValue("Achievements", "Username").empty())
|
||||
{
|
||||
Host::RunOnCPUThread(Achievements::Logout, true);
|
||||
Host::RunOnCPUThread([]() { Achievements::Logout(); }, true);
|
||||
updateLoginState();
|
||||
return;
|
||||
}
|
||||
|
@ -207,17 +203,12 @@ void AchievementSettingsWidget::onViewProfilePressed()
|
|||
return;
|
||||
|
||||
const QByteArray encoded_username(QUrl::toPercentEncoding(QString::fromStdString(username)));
|
||||
QtUtils::OpenURL(QtUtils::GetRootWidget(this),
|
||||
QtUtils::OpenURL(
|
||||
QtUtils::GetRootWidget(this),
|
||||
QUrl(QStringLiteral("https://retroachievements.org/user/%1").arg(QString::fromUtf8(encoded_username))));
|
||||
}
|
||||
|
||||
void AchievementSettingsWidget::onAchievementsRefreshed(quint32 id, const QString& game_info_string, quint32 total, quint32 points)
|
||||
void AchievementSettingsWidget::onAchievementsRefreshed(quint32 id, const QString& game_info_string)
|
||||
{
|
||||
m_ui.gameInfo->setText(game_info_string);
|
||||
}
|
||||
|
||||
void AchievementSettingsWidget::onNotificationsDurationChanged()
|
||||
{
|
||||
m_ui.notifications_duration_seconds->setText(tr("%1 seconds")
|
||||
.arg(m_ui.notifications_duration->value()));
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
/* PCSX2 - PS2 Emulator for PCs
|
||||
* Copyright (C) 2002-2022 PCSX2 Dev Team
|
||||
* Copyright (C) 2002-2023 PCSX2 Dev Team
|
||||
*
|
||||
* PCSX2 is free software: you can redistribute it and/or modify it under the terms
|
||||
* of the GNU Lesser General Public License as published by the Free Software Found-
|
||||
|
@ -29,15 +29,17 @@ public:
|
|||
|
||||
private Q_SLOTS:
|
||||
void updateEnableState();
|
||||
void onChallengeModeStateChanged();
|
||||
void onHardcoreModeStateChanged();
|
||||
void onAchievementsNotificationDurationSliderChanged();
|
||||
void onLeaderboardsNotificationDurationSliderChanged();
|
||||
void onLoginLogoutPressed();
|
||||
void onViewProfilePressed();
|
||||
void onAchievementsRefreshed(quint32 id, const QString& game_info_string, quint32 total, quint32 points);
|
||||
void onNotificationsDurationChanged();
|
||||
void onAchievementsRefreshed(quint32 id, const QString& game_info_string);
|
||||
|
||||
private:
|
||||
void updateLoginState();
|
||||
|
||||
Ui::AchievementSettingsWidget m_ui;
|
||||
|
||||
SettingsDialog* m_dialog;
|
||||
};
|
||||
|
|
|
@ -6,12 +6,12 @@
|
|||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>829</width>
|
||||
<height>641</height>
|
||||
<width>674</width>
|
||||
<height>481</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string notr="true">Form</string>
|
||||
<string>Form</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<property name="leftMargin">
|
||||
|
@ -29,9 +29,16 @@
|
|||
<item>
|
||||
<widget class="QGroupBox" name="groupBox">
|
||||
<property name="title">
|
||||
<string>Global Settings</string>
|
||||
<string>Settings</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<item row="1" column="1">
|
||||
<widget class="QCheckBox" name="spectatorMode">
|
||||
<property name="text">
|
||||
<string>Enable Spectator Mode</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<widget class="QCheckBox" name="enable">
|
||||
<property name="text">
|
||||
|
@ -39,52 +46,24 @@
|
|||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QCheckBox" name="richPresence">
|
||||
<property name="text">
|
||||
<string extracomment="This "Rich Presence" is not Discord's, but rather RetroAchivements own system.">Enable RA's Rich Presence</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QCheckBox" name="challengeMode">
|
||||
<property name="text">
|
||||
<string>Enable Hardcore Mode</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QCheckBox" name="leaderboards">
|
||||
<property name="text">
|
||||
<string>Enable Leaderboards</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<widget class="QCheckBox" name="unofficialTestMode">
|
||||
<item row="2" column="0">
|
||||
<widget class="QCheckBox" name="unofficialAchievements">
|
||||
<property name="text">
|
||||
<string>Test Unofficial Achievements</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QCheckBox" name="soundEffects">
|
||||
<item row="1" column="0">
|
||||
<widget class="QCheckBox" name="encoreMode">
|
||||
<property name="text">
|
||||
<string>Enable Sound Effects</string>
|
||||
<string>Enable Encore Mode</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QCheckBox" name="primedIndicators">
|
||||
<item row="0" column="1">
|
||||
<widget class="QCheckBox" name="hardcoreMode">
|
||||
<property name="text">
|
||||
<string>Show Challenge Indicators</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<widget class="QCheckBox" name="testMode">
|
||||
<property name="text">
|
||||
<string>Enable Test Mode</string>
|
||||
<string>Enable Hardcore Mode</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
|
@ -96,42 +75,16 @@
|
|||
<property name="title">
|
||||
<string>Notifications</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="notifications_box_layout" stretch="0,0">
|
||||
<layout class="QGridLayout" name="gridLayout_2" columnstretch="1,1">
|
||||
<item row="0" column="1">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_3" stretch="1,0">
|
||||
<item>
|
||||
<widget class="QCheckBox" name="notifications">
|
||||
<property name="text">
|
||||
<string>Show Notifications</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="notifications_duration_layout" stretch="0,0,0">
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QLabel" name="notifications_duration_label">
|
||||
<property name="text">
|
||||
<string>Duration</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QSlider" name="notifications_duration">
|
||||
<widget class="QSlider" name="achievementNotificationsDuration">
|
||||
<property name="minimum">
|
||||
<number>3</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>10</number>
|
||||
<number>30</number>
|
||||
</property>
|
||||
<property name="pageStep">
|
||||
<number>1</number>
|
||||
|
@ -154,7 +107,7 @@
|
|||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="notifications_duration_seconds">
|
||||
<widget class="QLabel" name="achievementNotificationsDurationLabel">
|
||||
<property name="text">
|
||||
<string>5 seconds</string>
|
||||
</property>
|
||||
|
@ -162,6 +115,73 @@
|
|||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<widget class="QCheckBox" name="achievementNotifications">
|
||||
<property name="text">
|
||||
<string>Show Achievement Notifications</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_2" stretch="1,0">
|
||||
<item>
|
||||
<widget class="QSlider" name="leaderboardNotificationsDuration">
|
||||
<property name="minimum">
|
||||
<number>3</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>30</number>
|
||||
</property>
|
||||
<property name="pageStep">
|
||||
<number>1</number>
|
||||
</property>
|
||||
<property name="value">
|
||||
<number>5</number>
|
||||
</property>
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="invertedAppearance">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="tickPosition">
|
||||
<enum>QSlider::TicksBelow</enum>
|
||||
</property>
|
||||
<property name="tickInterval">
|
||||
<number>1</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="leaderboardNotificationsDurationLabel">
|
||||
<property name="text">
|
||||
<string>5 seconds</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QCheckBox" name="leaderboardNotifications">
|
||||
<property name="text">
|
||||
<string>Show Leaderboard Notifications</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QCheckBox" name="soundEffects">
|
||||
<property name="text">
|
||||
<string>Enable Sound Effects</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QCheckBox" name="overlays">
|
||||
<property name="text">
|
||||
<string>Enable In-Game Overlays</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
|
@ -173,20 +193,17 @@
|
|||
<layout class="QHBoxLayout" name="horizontalLayout" stretch="1,0">
|
||||
<item>
|
||||
<widget class="QLabel" name="loginStatus">
|
||||
<property name="text">
|
||||
<string>Username:
|
||||
Login token generated at:</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||
<item>
|
||||
<widget class="QPushButton" name="loginButton">
|
||||
<property name="text">
|
||||
<string>Login...</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_4">
|
||||
<item>
|
||||
<widget class="QPushButton" name="viewProfile">
|
||||
<property name="text">
|
||||
|
@ -194,6 +211,13 @@
|
|||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="loginButton">
|
||||
<property name="text">
|
||||
<string>Login...</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
|
@ -204,7 +228,7 @@
|
|||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>80</height>
|
||||
<height>75</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="title">
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -19,14 +19,13 @@
|
|||
|
||||
#include "Config.h"
|
||||
|
||||
#include <functional>
|
||||
#include <mutex>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <tuple>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
class Error;
|
||||
|
||||
namespace Achievements
|
||||
{
|
||||
enum class LoginRequestReason
|
||||
|
@ -35,136 +34,115 @@ namespace Achievements
|
|||
TokenInvalid,
|
||||
};
|
||||
|
||||
enum class AchievementCategory : u8
|
||||
{
|
||||
Local = 0,
|
||||
Core = 3,
|
||||
Unofficial = 5
|
||||
};
|
||||
|
||||
struct Achievement
|
||||
{
|
||||
u32 id;
|
||||
std::string title;
|
||||
std::string description;
|
||||
std::string memaddr;
|
||||
std::string badge_name;
|
||||
|
||||
// badge paths are mutable because they're resolved when they're needed.
|
||||
mutable std::string locked_badge_path;
|
||||
mutable std::string unlocked_badge_path;
|
||||
|
||||
u32 points;
|
||||
AchievementCategory category;
|
||||
bool locked;
|
||||
bool active;
|
||||
bool primed;
|
||||
};
|
||||
|
||||
struct Leaderboard
|
||||
{
|
||||
u32 id;
|
||||
std::string title;
|
||||
std::string description;
|
||||
std::string memaddr;
|
||||
int format;
|
||||
bool active;
|
||||
};
|
||||
|
||||
struct LeaderboardEntry
|
||||
{
|
||||
std::string user;
|
||||
std::string formatted_score;
|
||||
time_t submitted;
|
||||
u32 rank;
|
||||
bool is_self;
|
||||
};
|
||||
|
||||
// RAIntegration only exists for Windows, so no point checking it on other platforms.
|
||||
#ifdef ENABLE_RAINTEGRATION
|
||||
bool IsUsingRAIntegration();
|
||||
#else
|
||||
__fi static bool IsUsingRAIntegration()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
bool IsActive();
|
||||
bool IsLoggedIn();
|
||||
bool HasSavedCredentials();
|
||||
bool ChallengeModeActive();
|
||||
bool LeaderboardsActive();
|
||||
bool IsTestModeActive();
|
||||
bool IsUnofficialTestModeActive();
|
||||
bool IsRichPresenceEnabled();
|
||||
bool HasActiveGame();
|
||||
|
||||
u32 GetGameID();
|
||||
|
||||
/// Acquires the achievements lock. Must be held when accessing any achievement state from another thread.
|
||||
std::unique_lock<std::recursive_mutex> GetLock();
|
||||
|
||||
void Initialize();
|
||||
/// Initializes the RetroAchievments client.
|
||||
bool Initialize();
|
||||
|
||||
/// Updates achievements settings.
|
||||
void UpdateSettings(const Pcsx2Config::AchievementsOptions& old_config);
|
||||
|
||||
/// Resets the internal state of all achievement tracking. Call on system reset.
|
||||
void ResetClient();
|
||||
|
||||
/// Called when the system is being reset. If it returns false, the reset should be aborted.
|
||||
bool OnReset();
|
||||
bool ConfirmSystemReset();
|
||||
|
||||
/// Called when the system is being shut down. If Shutdown() returns false, the shutdown should be aborted.
|
||||
bool Shutdown();
|
||||
bool Shutdown(bool allow_cancel);
|
||||
|
||||
/// Called when the system is being paused and resumed.
|
||||
void OnPaused(bool paused);
|
||||
void OnVMPaused(bool paused);
|
||||
|
||||
/// Called once a frame at vsync time on the CPU thread.
|
||||
void VSyncUpdate();
|
||||
void FrameUpdate();
|
||||
|
||||
/// Called to process pending HTTP requests when the VM is paused, because otherwise the vsync event won't fire.
|
||||
void ProcessPendingHTTPRequestsFromGSThread();
|
||||
/// Called when the system is paused, because FrameUpdate() won't be getting called.
|
||||
void IdleUpdate();
|
||||
|
||||
/// Saves/loads state.
|
||||
void LoadState(const u8* state_data, u32 state_data_size);
|
||||
std::vector<u8> SaveState();
|
||||
|
||||
/// Returns true if the current game has any achievements or leaderboards.
|
||||
/// Does not need to have the lock held.
|
||||
bool SafeHasAchievementsOrLeaderboards();
|
||||
/// Attempts to log in to RetroAchievements using the specified credentials.
|
||||
/// If the login is successful, the token returned by the server will be saved.
|
||||
bool Login(const char* username, const char* password, Error* error);
|
||||
|
||||
const std::string& GetUsername();
|
||||
const std::string& GetRichPresenceString();
|
||||
std::string SafeGetRichPresenceString();
|
||||
|
||||
bool LoginAsync(const char* username, const char* password);
|
||||
bool Login(const char* username, const char* password);
|
||||
bool LoginWithTokenAsync(const char* username, const char* api_token);
|
||||
/// Logs out of RetroAchievements, clearing any credentials.
|
||||
void Logout();
|
||||
|
||||
/// Called when the system changes game, or is booting.
|
||||
void GameChanged(u32 disc_crc, u32 crc);
|
||||
|
||||
/// Re-enables hardcode mode if it is enabled in the settings.
|
||||
bool ResetHardcoreMode();
|
||||
|
||||
/// Forces hardcore mode off until next reset.
|
||||
void DisableHardcoreMode();
|
||||
|
||||
/// Prompts the user to disable hardcore mode, if they agree, returns true.
|
||||
bool ConfirmHardcoreModeDisable(const char* trigger);
|
||||
|
||||
/// Returns true if hardcore mode is active, and functionality should be restricted.
|
||||
bool IsHardcoreModeActive();
|
||||
|
||||
/// RAIntegration only exists for Windows, so no point checking it on other platforms.
|
||||
bool IsUsingRAIntegration();
|
||||
|
||||
/// Returns true if the achievement system is active. Achievements can be active without a valid client.
|
||||
bool IsActive();
|
||||
|
||||
/// Returns true if RetroAchievements game data has been loaded.
|
||||
bool HasActiveGame();
|
||||
|
||||
/// Returns the RetroAchievements ID for the current game.
|
||||
u32 GetGameID();
|
||||
|
||||
/// Returns true if the current game has any achievements or leaderboards.
|
||||
bool HasAchievementsOrLeaderboards();
|
||||
|
||||
/// Returns true if the current game has any achievements.
|
||||
bool HasAchievements();
|
||||
|
||||
/// Returns true if the current game has any leaderboards.
|
||||
bool HasLeaderboards();
|
||||
|
||||
/// Returns true if the game supports rich presence.
|
||||
bool HasRichPresence();
|
||||
|
||||
/// Returns the current rich presence string.
|
||||
/// Should be called with the lock held.
|
||||
const std::string& GetRichPresenceString();
|
||||
|
||||
/// Returns the RetroAchievements title for the current game.
|
||||
/// Should be called with the lock held.
|
||||
const std::string& GetGameTitle();
|
||||
const std::string& GetGameIcon();
|
||||
|
||||
bool EnumerateAchievements(std::function<bool(const Achievement&)> callback);
|
||||
u32 GetUnlockedAchiementCount();
|
||||
u32 GetAchievementCount();
|
||||
u32 GetMaximumPointsForGame();
|
||||
u32 GetCurrentPointsForGame();
|
||||
/// Clears all cached state used to render the UI.
|
||||
void ClearUIState();
|
||||
|
||||
bool EnumerateLeaderboards(std::function<bool(const Leaderboard&)> callback);
|
||||
std::optional<bool> TryEnumerateLeaderboardEntries(u32 id, std::function<bool(const LeaderboardEntry&)> callback);
|
||||
const Leaderboard* GetLeaderboardByID(u32 id);
|
||||
u32 GetLeaderboardCount();
|
||||
bool IsLeaderboardTimeType(const Leaderboard& leaderboard);
|
||||
/// Draws ImGui overlays when not paused.
|
||||
void DrawGameOverlays();
|
||||
|
||||
const Achievement* GetAchievementByID(u32 id);
|
||||
std::pair<u32, u32> GetAchievementProgress(const Achievement& achievement);
|
||||
std::string GetAchievementProgressText(const Achievement& achievement);
|
||||
const std::string& GetAchievementBadgePath(
|
||||
const Achievement& achievement, bool download_if_missing = true, bool force_unlocked_icon = false);
|
||||
std::string GetAchievementBadgeURL(const Achievement& achievement);
|
||||
u32 GetPrimedAchievementCount();
|
||||
/// Draws ImGui overlays when paused.
|
||||
void DrawPauseMenuOverlays();
|
||||
|
||||
/// Queries the achievement list, and if no achievements are available, returns false.
|
||||
bool PrepareAchievementsWindow();
|
||||
|
||||
/// Renders the achievement list.
|
||||
void DrawAchievementsWindow();
|
||||
|
||||
/// Queries the leaderboard list, and if no leaderboards are available, returns false.
|
||||
bool PrepareLeaderboardsWindow();
|
||||
|
||||
/// Renders the leaderboard list.
|
||||
void DrawLeaderboardsWindow();
|
||||
|
||||
#ifdef ENABLE_RAINTEGRATION
|
||||
/// Prevents the internal implementation from being used. Instead, RAIntegration will be
|
||||
/// called into when achievement-related events occur.
|
||||
void SwitchToRAIntegration();
|
||||
|
||||
namespace RAIntegration
|
||||
|
@ -175,23 +153,21 @@ namespace Achievements
|
|||
void ActivateMenuItem(int item);
|
||||
} // namespace RAIntegration
|
||||
#endif
|
||||
|
||||
/// Re-enables hardcode mode if it is enabled in the settings.
|
||||
bool ResetChallengeMode();
|
||||
|
||||
/// Forces hardcore mode off until next reset.
|
||||
void DisableChallengeMode();
|
||||
|
||||
/// Prompts the user to disable hardcore mode, if they agree, returns true.
|
||||
bool ConfirmChallengeModeDisable(const char* trigger);
|
||||
|
||||
/// Returns true if features such as save states should be disabled.
|
||||
bool ChallengeModeActive();
|
||||
} // namespace Achievements
|
||||
|
||||
/// Functions implemented in the frontend.
|
||||
namespace Host
|
||||
{
|
||||
/// Called if the big picture UI requests achievements login, or token login fails.
|
||||
void OnAchievementsLoginRequested(Achievements::LoginRequestReason reason);
|
||||
|
||||
/// Called when achievements login completes.
|
||||
void OnAchievementsLoginSuccess(const char* display_name, u32 points, u32 sc_points, u32 unread_messages);
|
||||
|
||||
/// Called whenever game details or rich presence information is updated.
|
||||
/// Implementers can assume the lock is held when this is called.
|
||||
void OnAchievementsRefreshed();
|
||||
|
||||
/// Called whenever hardcore mode is toggled.
|
||||
void OnAchievementsHardcoreModeChanged(bool enabled);
|
||||
} // namespace Host
|
||||
|
|
|
@ -1243,33 +1243,32 @@ struct Pcsx2Config
|
|||
|
||||
struct AchievementsOptions
|
||||
{
|
||||
static constexpr u32 MINIMUM_NOTIFICATION_DURATION = 3;
|
||||
static constexpr u32 MAXIMUM_NOTIFICATION_DURATION = 30;
|
||||
static constexpr u32 DEFAULT_NOTIFICATION_DURATION = 5;
|
||||
static constexpr u32 DEFAULT_LEADERBOARD_DURATION = 10;
|
||||
|
||||
BITFIELD32()
|
||||
bool
|
||||
Enabled : 1,
|
||||
TestMode : 1,
|
||||
HardcoreMode : 1,
|
||||
EncoreMode : 1,
|
||||
SpectatorMode : 1,
|
||||
UnofficialTestMode : 1,
|
||||
RichPresence : 1,
|
||||
ChallengeMode : 1,
|
||||
Leaderboards : 1,
|
||||
Notifications : 1,
|
||||
LeaderboardNotifications : 1,
|
||||
SoundEffects : 1,
|
||||
PrimedIndicators : 1;
|
||||
Overlays : 1;
|
||||
BITFIELD_END
|
||||
|
||||
s32 NotificationsDuration = 5;
|
||||
u32 NotificationsDuration = DEFAULT_NOTIFICATION_DURATION;
|
||||
u32 LeaderboardsDuration = DEFAULT_LEADERBOARD_DURATION;
|
||||
|
||||
AchievementsOptions();
|
||||
void LoadSave(SettingsWrapper& wrap);
|
||||
|
||||
bool operator==(const AchievementsOptions& right) const
|
||||
{
|
||||
return OpEqu(bitset) && OpEqu(NotificationsDuration);
|
||||
}
|
||||
|
||||
bool operator!=(const AchievementsOptions& right) const
|
||||
{
|
||||
return !this->operator==(right);
|
||||
}
|
||||
bool operator==(const AchievementsOptions& right) const;
|
||||
bool operator!=(const AchievementsOptions& right) const;
|
||||
};
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
|
|
|
@ -40,7 +40,7 @@ void VMManager::Internal::ResetVMHotkeyState()
|
|||
|
||||
static void HotkeyAdjustTargetSpeed(double delta)
|
||||
{
|
||||
const double min_speed = Achievements::ChallengeModeActive() ? 1.0 : 0.1;
|
||||
const double min_speed = Achievements::IsHardcoreModeActive() ? 1.0 : 0.1;
|
||||
EmuConfig.EmulationSpeed.NominalScalar = std::max(min_speed, EmuConfig.EmulationSpeed.NominalScalar + delta);
|
||||
if (VMManager::GetLimiterMode() != LimiterModeType::Nominal)
|
||||
VMManager::SetLimiterMode(LimiterModeType::Nominal);
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,5 +1,5 @@
|
|||
/* PCSX2 - PS2 Emulator for PCs
|
||||
* Copyright (C) 2002-2022 PCSX2 Dev Team
|
||||
* Copyright (C) 2002-2023 PCSX2 Dev Team
|
||||
*
|
||||
* PCSX2 is free software: you can redistribute it and/or modify it under the terms
|
||||
* of the GNU Lesser General Public License as published by the Free Software Found-
|
||||
|
@ -14,8 +14,12 @@
|
|||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "common/Pcsx2Defs.h"
|
||||
#include "common/ProgressCallback.h"
|
||||
#include "common/SmallString.h"
|
||||
|
||||
#include <ctime>
|
||||
#include <string>
|
||||
#include <memory>
|
||||
|
||||
|
@ -31,12 +35,19 @@ namespace FullscreenUI
|
|||
void OnVMDestroyed();
|
||||
void GameChanged(std::string title, std::string path, std::string serial, u32 disc_crc, u32 crc);
|
||||
void OpenPauseMenu();
|
||||
void OpenAchievementsWindow();
|
||||
void OpenLeaderboardsWindow();
|
||||
bool OpenAchievementsWindow();
|
||||
bool OpenLeaderboardsWindow();
|
||||
|
||||
// NOTE: Only call from GS thread.
|
||||
bool IsAchievementsWindowOpen();
|
||||
bool IsLeaderboardsWindowOpen();
|
||||
void ReturnToPreviousWindow();
|
||||
void ReturnToMainWindow();
|
||||
|
||||
void Shutdown(bool clear_state);
|
||||
void Render();
|
||||
void InvalidateCoverCache();
|
||||
TinyString TimeToPrintableString(time_t t);
|
||||
|
||||
class ProgressCallback final : public BaseProgressCallback
|
||||
{
|
||||
|
|
|
@ -90,6 +90,7 @@ namespace ImGuiFullscreen
|
|||
static u32 s_menu_button_index = 0;
|
||||
static u32 s_close_button_state = 0;
|
||||
static bool s_focus_reset_queued = false;
|
||||
static bool s_light_theme = false;
|
||||
|
||||
static LRUCache<std::string, std::shared_ptr<GSTexture>> s_texture_cache(128, true);
|
||||
static std::shared_ptr<GSTexture> s_placeholder_texture;
|
||||
|
@ -152,13 +153,20 @@ namespace ImGuiFullscreen
|
|||
static std::vector<std::string> s_file_selector_filters;
|
||||
static std::vector<FileSelectorItem> s_file_selector_items;
|
||||
|
||||
static constexpr float NOTIFICATION_FADE_IN_TIME = 0.2f;
|
||||
static constexpr float NOTIFICATION_FADE_OUT_TIME = 0.8f;
|
||||
|
||||
struct Notification
|
||||
{
|
||||
std::string key;
|
||||
std::string title;
|
||||
std::string text;
|
||||
std::string badge_path;
|
||||
Common::Timer::Value start_time;
|
||||
Common::Timer::Value move_time;
|
||||
float duration;
|
||||
float target_y;
|
||||
float last_y;
|
||||
};
|
||||
|
||||
static std::vector<Notification> s_notifications;
|
||||
|
@ -1574,6 +1582,88 @@ bool ImGuiFullscreen::NavButton(const char* title, bool is_active, bool enabled
|
|||
return pressed;
|
||||
}
|
||||
|
||||
|
||||
bool ImGuiFullscreen::NavTab(const char* title, bool is_active, bool enabled /* = true */, float width, float height,
|
||||
const ImVec4& background, ImFont* font /* = g_large_font */)
|
||||
{
|
||||
ImGuiWindow* window = ImGui::GetCurrentWindow();
|
||||
if (window->SkipItems)
|
||||
return false;
|
||||
|
||||
s_menu_button_index++;
|
||||
|
||||
const ImVec2 text_size(font->CalcTextSizeA(font->FontSize, std::numeric_limits<float>::max(), 0.0f, title));
|
||||
const ImVec2 pos(window->DC.CursorPos);
|
||||
const ImVec2 size = ImVec2(((width < 0.0f) ? text_size.x : LayoutScale(width)), LayoutScale(height));
|
||||
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0.0f, 0.0f));
|
||||
ImGui::ItemSize(ImVec2(size.x, size.y));
|
||||
ImGui::SameLine();
|
||||
ImGui::PopStyleVar();
|
||||
|
||||
ImRect bb(pos, pos + size);
|
||||
const ImGuiID id = window->GetID(title);
|
||||
if (enabled)
|
||||
{
|
||||
// bit contradictory - we don't want this button to be used for *gamepad* navigation, since they're usually
|
||||
// activated with the bumpers and/or the back button.
|
||||
if (!ImGui::ItemAdd(bb, id, nullptr, ImGuiItemFlags_NoNav | ImGuiItemFlags_NoNavDefaultFocus))
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (ImGui::IsClippedEx(bb, id))
|
||||
return false;
|
||||
}
|
||||
|
||||
bool held;
|
||||
bool pressed;
|
||||
bool hovered;
|
||||
if (enabled)
|
||||
{
|
||||
pressed = ImGui::ButtonBehavior(bb, id, &hovered, &held, ImGuiButtonFlags_NoNavFocus);
|
||||
}
|
||||
else
|
||||
{
|
||||
pressed = false;
|
||||
held = false;
|
||||
hovered = false;
|
||||
}
|
||||
|
||||
const ImU32 col =
|
||||
hovered ? ImGui::GetColorU32(held ? ImGuiCol_ButtonActive : ImGuiCol_ButtonHovered, 1.0f) :
|
||||
ImGui::GetColorU32(is_active ? background : ImVec4(background.x, background.y, background.z, 0.5f));
|
||||
|
||||
ImGui::RenderFrame(bb.Min, bb.Max, col, true, 0.0f);
|
||||
|
||||
#if 0
|
||||
// This looks a bit rubbish... but left it here if someone thinks they can improve it.
|
||||
if (is_active)
|
||||
{
|
||||
const float line_thickness = LayoutScale(2.0f);
|
||||
ImGui::GetWindowDrawList()->AddLine(ImVec2(bb.Min.x, bb.Max.y - line_thickness),
|
||||
ImVec2(bb.Max.x, bb.Max.y - line_thickness),
|
||||
ImGui::GetColorU32(ImGuiCol_TextDisabled), line_thickness);
|
||||
}
|
||||
#endif
|
||||
|
||||
const ImVec2 pad(std::max((size.x - text_size.x) * 0.5f, 0.0f), std::max((size.y - text_size.y) * 0.5f, 0.0f));
|
||||
bb.Min += pad;
|
||||
bb.Max -= pad;
|
||||
|
||||
ImGui::PushStyleColor(
|
||||
ImGuiCol_Text,
|
||||
ImGui::GetColorU32(enabled ? (is_active ? ImGuiCol_Text : ImGuiCol_TextDisabled) : ImGuiCol_ButtonHovered));
|
||||
|
||||
ImGui::PushFont(font);
|
||||
ImGui::RenderTextClipped(bb.Min, bb.Max, title, nullptr, nullptr, ImVec2(0.0f, 0.0f), &bb);
|
||||
ImGui::PopFont();
|
||||
|
||||
ImGui::PopStyleColor();
|
||||
|
||||
return pressed;
|
||||
}
|
||||
|
||||
void ImGuiFullscreen::PopulateFileSelectorItems()
|
||||
{
|
||||
s_file_selector_items.clear();
|
||||
|
@ -2276,14 +2366,42 @@ void ImGuiFullscreen::DrawBackgroundProgressDialogs(ImVec2& position, float spac
|
|||
// Notifications
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
void ImGuiFullscreen::AddNotification(float duration, std::string title, std::string text, std::string image_path)
|
||||
void ImGuiFullscreen::AddNotification(std::string key, float duration, std::string title, std::string text,
|
||||
std::string image_path)
|
||||
{
|
||||
const Common::Timer::Value current_time = Common::Timer::GetCurrentValue();
|
||||
|
||||
if (!key.empty())
|
||||
{
|
||||
for (auto it = s_notifications.begin(); it != s_notifications.end(); ++it)
|
||||
{
|
||||
if (it->key == key)
|
||||
{
|
||||
it->duration = duration;
|
||||
it->title = std::move(title);
|
||||
it->text = std::move(text);
|
||||
it->badge_path = std::move(image_path);
|
||||
|
||||
// Don't fade it in again
|
||||
const float time_passed =
|
||||
static_cast<float>(Common::Timer::ConvertValueToSeconds(current_time - it->start_time));
|
||||
it->start_time =
|
||||
current_time - Common::Timer::ConvertSecondsToValue(std::min(time_passed, NOTIFICATION_FADE_IN_TIME));
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Notification notif;
|
||||
notif.key = std::move(key);
|
||||
notif.duration = duration;
|
||||
notif.title = std::move(title);
|
||||
notif.text = std::move(text);
|
||||
notif.badge_path = std::move(image_path);
|
||||
notif.start_time = Common::Timer::GetCurrentValue();
|
||||
notif.start_time = current_time;
|
||||
notif.move_time = current_time;
|
||||
notif.target_y = -1.0f;
|
||||
notif.last_y = -1.0f;
|
||||
s_notifications.push_back(std::move(notif));
|
||||
}
|
||||
|
||||
|
@ -2297,8 +2415,7 @@ void ImGuiFullscreen::DrawNotifications(ImVec2& position, float spacing)
|
|||
if (s_notifications.empty())
|
||||
return;
|
||||
|
||||
static constexpr float EASE_IN_TIME = 0.6f;
|
||||
static constexpr float EASE_OUT_TIME = 0.6f;
|
||||
static constexpr float MOVE_DURATION = 0.5f;
|
||||
const Common::Timer::Value current_time = Common::Timer::GetCurrentValue();
|
||||
|
||||
const float horizontal_padding = ImGuiFullscreen::LayoutScale(20.0f);
|
||||
|
@ -2316,21 +2433,14 @@ void ImGuiFullscreen::DrawNotifications(ImVec2& position, float spacing)
|
|||
ImFont* const title_font = ImGuiFullscreen::g_large_font;
|
||||
ImFont* const text_font = ImGuiFullscreen::g_medium_font;
|
||||
|
||||
#if 0
|
||||
static constexpr u32 toast_background_color = IM_COL32(241, 241, 241, 255);
|
||||
static constexpr u32 toast_border_color = IM_COL32(0x88, 0x88, 0x88, 255);
|
||||
static constexpr u32 toast_title_color = IM_COL32(1, 1, 1, 255);
|
||||
static constexpr u32 toast_text_color = IM_COL32(0, 0, 0, 255);
|
||||
#else
|
||||
static constexpr u32 toast_background_color = IM_COL32(0x21, 0x21, 0x21, 255);
|
||||
static constexpr u32 toast_border_color = IM_COL32(0x48, 0x48, 0x48, 255);
|
||||
static constexpr u32 toast_title_color = IM_COL32(0xff, 0xff, 0xff, 255);
|
||||
static constexpr u32 toast_text_color = IM_COL32(0xff, 0xff, 0xff, 255);
|
||||
#endif
|
||||
const u32 toast_background_color = s_light_theme ? IM_COL32(241, 241, 241, 255) : IM_COL32(0x21, 0x21, 0x21, 255);
|
||||
const u32 toast_border_color = s_light_theme ? IM_COL32(0x88, 0x88, 0x88, 255) : IM_COL32(0x48, 0x48, 0x48, 255);
|
||||
const u32 toast_title_color = s_light_theme ? IM_COL32(1, 1, 1, 255) : IM_COL32(0xff, 0xff, 0xff, 255);
|
||||
const u32 toast_text_color = s_light_theme ? IM_COL32(0, 0, 0, 255) : IM_COL32(0xff, 0xff, 0xff, 255);
|
||||
|
||||
for (u32 index = 0; index < static_cast<u32>(s_notifications.size());)
|
||||
{
|
||||
const Notification& notif = s_notifications[index];
|
||||
Notification& notif = s_notifications[index];
|
||||
const float time_passed = static_cast<float>(Common::Timer::ConvertValueToSeconds(current_time - notif.start_time));
|
||||
if (time_passed >= notif.duration)
|
||||
{
|
||||
|
@ -2338,36 +2448,62 @@ void ImGuiFullscreen::DrawNotifications(ImVec2& position, float spacing)
|
|||
continue;
|
||||
}
|
||||
|
||||
const ImVec2 title_size(text_font->CalcTextSizeA(
|
||||
title_font->FontSize, max_text_width, max_text_width, notif.title.c_str(), notif.title.c_str() + notif.title.size()));
|
||||
const ImVec2 title_size(text_font->CalcTextSizeA(title_font->FontSize, max_text_width, max_text_width,
|
||||
notif.title.c_str(), notif.title.c_str() + notif.title.size()));
|
||||
|
||||
const ImVec2 text_size(text_font->CalcTextSizeA(
|
||||
text_font->FontSize, max_text_width, max_text_width, notif.text.c_str(), notif.text.c_str() + notif.text.size()));
|
||||
const ImVec2 text_size(text_font->CalcTextSizeA(text_font->FontSize, max_text_width, max_text_width,
|
||||
notif.text.c_str(), notif.text.c_str() + notif.text.size()));
|
||||
|
||||
const float box_width =
|
||||
std::max((horizontal_padding * 2.0f) + badge_size + horizontal_spacing + std::max(title_size.x, text_size.x), min_width);
|
||||
const float box_height = std::max((vertical_padding * 2.0f) + title_size.y + vertical_spacing + text_size.y, min_height);
|
||||
const float box_width = std::max(
|
||||
(horizontal_padding * 2.0f) + badge_size + horizontal_spacing + std::max(title_size.x, text_size.x), min_width);
|
||||
const float box_height =
|
||||
std::max((vertical_padding * 2.0f) + title_size.y + vertical_spacing + text_size.y, min_height);
|
||||
|
||||
float x_offset = 0.0f;
|
||||
if (time_passed < EASE_IN_TIME)
|
||||
u8 opacity;
|
||||
if (time_passed < NOTIFICATION_FADE_IN_TIME)
|
||||
opacity = static_cast<u8>((time_passed / NOTIFICATION_FADE_IN_TIME) * 255.0f);
|
||||
else if (time_passed > (notif.duration - NOTIFICATION_FADE_OUT_TIME))
|
||||
opacity = static_cast<u8>(std::min((notif.duration - time_passed) / NOTIFICATION_FADE_OUT_TIME, 1.0f) * 255.0f);
|
||||
else
|
||||
opacity = 255;
|
||||
|
||||
const float expected_y = position.y - ((s_notification_vertical_direction < 0.0f) ? box_height : 0.0f);
|
||||
float actual_y = notif.last_y;
|
||||
if (notif.target_y != expected_y)
|
||||
{
|
||||
const float disp = (box_width + position.x);
|
||||
x_offset = -(disp - (disp * Easing::InBack(time_passed / EASE_IN_TIME)));
|
||||
notif.move_time = current_time;
|
||||
notif.target_y = expected_y;
|
||||
notif.last_y = (notif.last_y < 0.0f) ? expected_y : notif.last_y;
|
||||
actual_y = notif.last_y;
|
||||
}
|
||||
else if (time_passed > (notif.duration - EASE_OUT_TIME))
|
||||
else if (actual_y != expected_y)
|
||||
{
|
||||
const float disp = (box_width + position.x);
|
||||
x_offset = -(disp - (disp * Easing::OutBack((notif.duration - time_passed) / EASE_OUT_TIME)));
|
||||
const float time_since_move =
|
||||
static_cast<float>(Common::Timer::ConvertValueToSeconds(current_time - notif.move_time));
|
||||
if (time_since_move >= MOVE_DURATION)
|
||||
{
|
||||
notif.move_time = current_time;
|
||||
notif.last_y = notif.target_y;
|
||||
actual_y = notif.last_y;
|
||||
}
|
||||
else
|
||||
{
|
||||
const float frac = Easing::OutExpo(time_since_move / MOVE_DURATION);
|
||||
actual_y = notif.last_y - ((notif.last_y - notif.target_y) * frac);
|
||||
}
|
||||
}
|
||||
|
||||
const ImVec2 box_min(position.x + x_offset, position.y - ((s_notification_vertical_direction < 0.0f) ? box_height : 0.0f));
|
||||
const ImVec2 box_min(position.x, actual_y);
|
||||
const ImVec2 box_max(box_min.x + box_width, box_min.y + box_height);
|
||||
const u32 background_color = (toast_background_color & ~IM_COL32_A_MASK) | (opacity << IM_COL32_A_SHIFT);
|
||||
const u32 border_color = (toast_border_color & ~IM_COL32_A_MASK) | (opacity << IM_COL32_A_SHIFT);
|
||||
|
||||
ImDrawList* dl = ImGui::GetForegroundDrawList();
|
||||
dl->AddRectFilled(ImVec2(box_min.x + shadow_size, box_min.y + shadow_size),
|
||||
ImVec2(box_max.x + shadow_size, box_max.y + shadow_size), IM_COL32(20, 20, 20, 180), rounding, ImDrawCornerFlags_All);
|
||||
dl->AddRectFilled(box_min, box_max, toast_background_color, rounding, ImDrawCornerFlags_All);
|
||||
dl->AddRect(box_min, box_max, toast_border_color, rounding, ImDrawCornerFlags_All, ImGuiFullscreen::LayoutScale(1.0f));
|
||||
ImVec2(box_max.x + shadow_size, box_max.y + shadow_size),
|
||||
IM_COL32(20, 20, 20, (180 * opacity) / 255u), rounding, ImDrawCornerFlags_All);
|
||||
dl->AddRectFilled(box_min, box_max, background_color, rounding, ImDrawCornerFlags_All);
|
||||
dl->AddRect(box_min, box_max, border_color, rounding, ImDrawCornerFlags_All, ImGuiFullscreen::LayoutScale(1.0f));
|
||||
|
||||
const ImVec2 badge_min(box_min.x + horizontal_padding, box_min.y + vertical_padding);
|
||||
const ImVec2 badge_max(badge_min.x + badge_size, badge_min.y + badge_size);
|
||||
|
@ -2375,18 +2511,23 @@ void ImGuiFullscreen::DrawNotifications(ImVec2& position, float spacing)
|
|||
{
|
||||
GSTexture* tex = GetCachedTexture(notif.badge_path.c_str());
|
||||
if (tex)
|
||||
dl->AddImage(tex->GetNativeHandle(), badge_min, badge_max);
|
||||
{
|
||||
dl->AddImage(tex, badge_min, badge_max, ImVec2(0.0f, 0.0f), ImVec2(1.0f, 1.0f),
|
||||
IM_COL32(255, 255, 255, opacity));
|
||||
}
|
||||
}
|
||||
|
||||
const ImVec2 title_min(badge_max.x + horizontal_spacing, box_min.y + vertical_padding);
|
||||
const ImVec2 title_max(title_min.x + title_size.x, title_min.y + title_size.y);
|
||||
dl->AddText(title_font, title_font->FontSize, title_min, toast_title_color, notif.title.c_str(),
|
||||
const u32 title_col = (toast_title_color & ~IM_COL32_A_MASK) | (opacity << IM_COL32_A_SHIFT);
|
||||
dl->AddText(title_font, title_font->FontSize, title_min, title_col, notif.title.c_str(),
|
||||
notif.title.c_str() + notif.title.size(), max_text_width);
|
||||
|
||||
const ImVec2 text_min(badge_max.x + horizontal_spacing, title_max.y + vertical_spacing);
|
||||
const ImVec2 text_max(text_min.x + text_size.x, text_min.y + text_size.y);
|
||||
dl->AddText(text_font, text_font->FontSize, text_min, toast_text_color, notif.text.c_str(), notif.text.c_str() + notif.text.size(),
|
||||
max_text_width);
|
||||
const u32 text_col = (toast_text_color & ~IM_COL32_A_MASK) | (opacity << IM_COL32_A_SHIFT);
|
||||
dl->AddText(text_font, text_font->FontSize, text_min, text_col, notif.text.c_str(),
|
||||
notif.text.c_str() + notif.text.size(), max_text_width);
|
||||
|
||||
position.y += s_notification_vertical_direction * (box_height + shadow_size + spacing);
|
||||
index++;
|
||||
|
@ -2464,6 +2605,8 @@ void ImGuiFullscreen::DrawToast()
|
|||
|
||||
void ImGuiFullscreen::SetTheme(bool light)
|
||||
{
|
||||
s_light_theme = light;
|
||||
|
||||
if (!light)
|
||||
{
|
||||
// dark
|
||||
|
|
|
@ -213,6 +213,8 @@ namespace ImGuiFullscreen
|
|||
float item_height = LAYOUT_MENU_BUTTON_HEIGHT_NO_SUMMARY);
|
||||
bool NavButton(const char* title, bool is_active, bool enabled = true, float width = -1.0f,
|
||||
float height = LAYOUT_MENU_BUTTON_HEIGHT_NO_SUMMARY, ImFont* font = g_large_font);
|
||||
bool NavTab(const char* title, bool is_active, bool enabled, float width, float height, const ImVec4& background,
|
||||
ImFont* font = g_large_font);
|
||||
|
||||
using FileSelectorCallback = std::function<void(const std::string& path)>;
|
||||
using FileSelectorFilters = std::vector<std::string>;
|
||||
|
@ -253,7 +255,7 @@ namespace ImGuiFullscreen
|
|||
void UpdateBackgroundProgressDialog(const char* str_id, std::string message, s32 min, s32 max, s32 value);
|
||||
void CloseBackgroundProgressDialog(const char* str_id);
|
||||
|
||||
void AddNotification(float duration, std::string title, std::string text, std::string image_path);
|
||||
void AddNotification(std::string key, float duration, std::string title, std::string text, std::string image_path);
|
||||
void ClearNotifications();
|
||||
|
||||
void ShowToast(std::string title, std::string message, float duration = 10.0f);
|
||||
|
|
|
@ -354,7 +354,7 @@ void Patch::EnumeratePnachFiles(const std::string_view& serial, u32 crc, bool ch
|
|||
{
|
||||
// Prefer files on disk over the zip.
|
||||
std::vector<std::string> disk_patch_files;
|
||||
if (for_ui || !Achievements::ChallengeModeActive())
|
||||
if (for_ui || !Achievements::IsHardcoreModeActive())
|
||||
disk_patch_files = FindPatchFilesOnDisk(serial, crc, cheats, for_ui);
|
||||
|
||||
if (!disk_patch_files.empty())
|
||||
|
@ -478,7 +478,7 @@ std::string Patch::GetPnachFilename(const std::string_view& serial, u32 crc, boo
|
|||
|
||||
void Patch::ReloadEnabledLists()
|
||||
{
|
||||
if (EmuConfig.EnableCheats && !Achievements::ChallengeModeActive())
|
||||
if (EmuConfig.EnableCheats && !Achievements::IsHardcoreModeActive())
|
||||
s_enabled_cheats = Host::GetStringListSetting(CHEATS_CONFIG_SECTION, PATCH_ENABLE_CONFIG_KEY);
|
||||
else
|
||||
s_enabled_cheats = {};
|
||||
|
|
|
@ -1431,15 +1431,14 @@ bool Pcsx2Config::PadOptions::Port::operator!=(const PadOptions::Port& right) co
|
|||
Pcsx2Config::AchievementsOptions::AchievementsOptions()
|
||||
{
|
||||
Enabled = false;
|
||||
TestMode = false;
|
||||
HardcoreMode = false;
|
||||
EncoreMode = false;
|
||||
SpectatorMode = false;
|
||||
UnofficialTestMode = false;
|
||||
RichPresence = true;
|
||||
ChallengeMode = false;
|
||||
Leaderboards = true;
|
||||
Notifications = true;
|
||||
LeaderboardNotifications = true;
|
||||
SoundEffects = true;
|
||||
PrimedIndicators = true;
|
||||
NotificationsDuration = 5;
|
||||
Overlays = true;
|
||||
}
|
||||
|
||||
void Pcsx2Config::AchievementsOptions::LoadSave(SettingsWrapper& wrap)
|
||||
|
@ -1447,23 +1446,35 @@ void Pcsx2Config::AchievementsOptions::LoadSave(SettingsWrapper& wrap)
|
|||
SettingsWrapSection("Achievements");
|
||||
|
||||
SettingsWrapBitBool(Enabled);
|
||||
SettingsWrapBitBool(TestMode);
|
||||
SettingsWrapBitBoolEx(HardcoreMode, "ChallengeMode");
|
||||
SettingsWrapBitBool(EncoreMode);
|
||||
SettingsWrapBitBool(SpectatorMode);
|
||||
SettingsWrapBitBool(UnofficialTestMode);
|
||||
SettingsWrapBitBool(RichPresence);
|
||||
SettingsWrapBitBool(ChallengeMode);
|
||||
SettingsWrapBitBool(Leaderboards);
|
||||
SettingsWrapBitBool(Notifications);
|
||||
SettingsWrapBitBool(LeaderboardNotifications);
|
||||
SettingsWrapBitBool(SoundEffects);
|
||||
SettingsWrapBitBool(PrimedIndicators);
|
||||
SettingsWrapBitfield(NotificationsDuration);
|
||||
SettingsWrapBitBool(Overlays);
|
||||
SettingsWrapEntry(NotificationsDuration);
|
||||
SettingsWrapEntry(LeaderboardsDuration);
|
||||
|
||||
if (wrap.IsLoading())
|
||||
{
|
||||
//Clamp in case setting was updated manually using the INI
|
||||
NotificationsDuration = std::clamp(NotificationsDuration, 3, 10);
|
||||
NotificationsDuration = std::clamp(NotificationsDuration, MINIMUM_NOTIFICATION_DURATION, MAXIMUM_NOTIFICATION_DURATION);
|
||||
LeaderboardsDuration = std::clamp(LeaderboardsDuration, MINIMUM_NOTIFICATION_DURATION, MAXIMUM_NOTIFICATION_DURATION);
|
||||
}
|
||||
}
|
||||
|
||||
bool Pcsx2Config::AchievementsOptions::operator==(const AchievementsOptions& right) const
|
||||
{
|
||||
return OpEqu(bitset) && OpEqu(NotificationsDuration) && OpEqu(LeaderboardsDuration);
|
||||
}
|
||||
|
||||
bool Pcsx2Config::AchievementsOptions::operator!=(const AchievementsOptions& right) const
|
||||
{
|
||||
return !this->operator==(right);
|
||||
}
|
||||
|
||||
Pcsx2Config::Pcsx2Config()
|
||||
{
|
||||
bitset = 0;
|
||||
|
|
|
@ -59,6 +59,7 @@
|
|||
#include "common/FileSystem.h"
|
||||
#include "common/ScopedGuard.h"
|
||||
#include "common/SettingsWrapper.h"
|
||||
#include "common/SmallString.h"
|
||||
#include "common/StringUtil.h"
|
||||
#include "common/Threading.h"
|
||||
#include "common/Timer.h"
|
||||
|
@ -256,7 +257,7 @@ void VMManager::SetState(VMState state)
|
|||
}
|
||||
|
||||
SPU2::SetOutputPaused(paused);
|
||||
Achievements::OnPaused(paused);
|
||||
Achievements::OnVMPaused(paused);
|
||||
|
||||
UpdateInhibitScreensaver(!paused && EmuConfig.InhibitScreensaver);
|
||||
|
||||
|
@ -381,7 +382,7 @@ void VMManager::Internal::CPUThreadShutdown()
|
|||
|
||||
s_pine_server.Deinitialize();
|
||||
|
||||
Achievements::Shutdown();
|
||||
Achievements::Shutdown(false);
|
||||
|
||||
InputManager::CloseSources();
|
||||
WaitForSaveStateFlush();
|
||||
|
@ -911,16 +912,12 @@ void VMManager::UpdateDiscDetails(bool booting)
|
|||
// Patches are game-dependent, thus should get applied after game settings ia loaded.
|
||||
Patch::ReloadPatches(s_disc_serial, HasBootedELF() ? s_current_crc : 0, true, true, false, false);
|
||||
|
||||
// Per-game ini enabling of hardcore mode. We need to re-enforce the settings if so.
|
||||
if (booting && Achievements::ResetChallengeMode())
|
||||
ApplySettings();
|
||||
|
||||
ReportGameChangeToHost();
|
||||
Achievements::GameChanged(s_disc_crc, s_current_crc);
|
||||
if (MTGS::IsOpen())
|
||||
MTGS::GameChanged();
|
||||
ReloadPINE();
|
||||
UpdateDiscordPresence(Achievements::GetRichPresenceString());
|
||||
UpdateDiscordPresence();
|
||||
|
||||
if (!GSDumpReplayer::IsReplayingDump())
|
||||
FileMcd_Reopen(memcardFilters.empty() ? s_disc_serial : memcardFilters);
|
||||
|
@ -1182,9 +1179,9 @@ bool VMManager::Initialize(VMBootParameters boot_params)
|
|||
}
|
||||
|
||||
// Check for resuming with hardcore mode.
|
||||
Achievements::ResetChallengeMode();
|
||||
if (!state_to_load.empty() && Achievements::ChallengeModeActive() &&
|
||||
!Achievements::ConfirmChallengeModeDisable("Resuming state"))
|
||||
Achievements::ResetHardcoreMode();
|
||||
if (!state_to_load.empty() && Achievements::IsHardcoreModeActive() &&
|
||||
!Achievements::ConfirmHardcoreModeDisable("Resuming state"))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
@ -1353,7 +1350,7 @@ void VMManager::Shutdown(bool save_resume_state)
|
|||
|
||||
Achievements::GameChanged(0, 0);
|
||||
FullscreenUI::GameChanged(s_title, std::string(), s_disc_serial, 0, 0);
|
||||
UpdateDiscordPresence(Achievements::GetRichPresenceString());
|
||||
UpdateDiscordPresence();
|
||||
Host::OnGameChanged(s_title, std::string(), std::string(), s_disc_serial, 0, 0);
|
||||
|
||||
s_fast_boot_requested = false;
|
||||
|
@ -1420,11 +1417,11 @@ void VMManager::Reset()
|
|||
return;
|
||||
}
|
||||
|
||||
if (!Achievements::OnReset())
|
||||
if (!Achievements::ConfirmSystemReset())
|
||||
return;
|
||||
|
||||
// Re-enforce hardcode mode constraints if we're now enabling it.
|
||||
if (Achievements::ResetChallengeMode())
|
||||
if (Achievements::ResetHardcoreMode())
|
||||
ApplySettings();
|
||||
|
||||
vu1Thread.WaitVU();
|
||||
|
@ -1685,7 +1682,7 @@ u32 VMManager::DeleteSaveStates(const char* game_serial, u32 game_crc, bool also
|
|||
|
||||
bool VMManager::LoadState(const char* filename)
|
||||
{
|
||||
if (Achievements::ChallengeModeActive() && !Achievements::ConfirmChallengeModeDisable("Loading state"))
|
||||
if (Achievements::IsHardcoreModeActive() && !Achievements::ConfirmHardcoreModeDisable("Loading state"))
|
||||
return false;
|
||||
|
||||
// TODO: Save the current state so we don't need to reset.
|
||||
|
@ -1707,7 +1704,7 @@ bool VMManager::LoadStateFromSlot(s32 slot)
|
|||
return false;
|
||||
}
|
||||
|
||||
if (Achievements::ChallengeModeActive() && !Achievements::ConfirmChallengeModeDisable("Loading state"))
|
||||
if (Achievements::IsHardcoreModeActive() && !Achievements::ConfirmHardcoreModeDisable("Loading state"))
|
||||
return false;
|
||||
|
||||
Host::AddIconOSDMessage("LoadStateFromSlot", ICON_FA_FOLDER_OPEN,
|
||||
|
@ -1895,7 +1892,7 @@ void VMManager::FrameAdvance(u32 num_frames /*= 1*/)
|
|||
if (!HasValidVM())
|
||||
return;
|
||||
|
||||
if (Achievements::ChallengeModeActive() && !Achievements::ConfirmChallengeModeDisable("Frame advancing"))
|
||||
if (Achievements::IsHardcoreModeActive() && !Achievements::ConfirmHardcoreModeDisable("Frame advancing"))
|
||||
return;
|
||||
|
||||
s_frame_advance_count = num_frames;
|
||||
|
@ -2034,6 +2031,15 @@ void VMManager::Execute()
|
|||
Cpu->Execute();
|
||||
}
|
||||
|
||||
void VMManager::IdlePollUpdate()
|
||||
{
|
||||
Achievements::IdleUpdate();
|
||||
|
||||
PollDiscordPresence();
|
||||
|
||||
InputManager::PollSources();
|
||||
}
|
||||
|
||||
void VMManager::SetPaused(bool paused)
|
||||
{
|
||||
if (!HasValidVM())
|
||||
|
@ -2155,8 +2161,7 @@ void VMManager::Internal::VSyncOnCPUThread()
|
|||
}
|
||||
}
|
||||
|
||||
if (Achievements::IsActive())
|
||||
Achievements::VSyncUpdate();
|
||||
Achievements::FrameUpdate();
|
||||
|
||||
PollDiscordPresence();
|
||||
|
||||
|
@ -2385,7 +2390,7 @@ void VMManager::ReloadPatches(bool reload_files, bool reload_enabled_list, bool
|
|||
|
||||
void VMManager::EnforceAchievementsChallengeModeSettings()
|
||||
{
|
||||
if (!Achievements::ChallengeModeActive())
|
||||
if (!Achievements::IsHardcoreModeActive())
|
||||
{
|
||||
Host::RemoveKeyedOSDMessage("ChallengeDisableCheats");
|
||||
return;
|
||||
|
@ -2970,7 +2975,7 @@ void VMManager::InitializeDiscordPresence()
|
|||
Discord_Initialize("1025789002055430154", &handlers, 0, nullptr);
|
||||
s_discord_presence_active = true;
|
||||
|
||||
UpdateDiscordPresence(Achievements::GetRichPresenceString());
|
||||
UpdateDiscordPresence();
|
||||
}
|
||||
|
||||
void VMManager::ShutdownDiscordPresence()
|
||||
|
@ -2984,7 +2989,7 @@ void VMManager::ShutdownDiscordPresence()
|
|||
s_discord_presence_active = false;
|
||||
}
|
||||
|
||||
void VMManager::UpdateDiscordPresence(const std::string& rich_presence)
|
||||
void VMManager::UpdateDiscordPresence()
|
||||
{
|
||||
if (!s_discord_presence_active)
|
||||
return;
|
||||
|
@ -2996,18 +3001,12 @@ void VMManager::UpdateDiscordPresence(const std::string& rich_presence)
|
|||
rp.startTimestamp = std::time(nullptr);
|
||||
rp.details = s_title.empty() ? "No Game Running" : s_title.c_str();
|
||||
|
||||
// Trim to 128 bytes as per Discord-RPC requirements
|
||||
std::string state_string;
|
||||
if (rich_presence.length() >= 128)
|
||||
if (Achievements::HasRichPresence())
|
||||
{
|
||||
// 124 characters + 3 dots + null terminator
|
||||
state_string = fmt::format("{}...", std::string_view(rich_presence).substr(0, 124));
|
||||
}
|
||||
else
|
||||
{
|
||||
state_string = rich_presence;
|
||||
}
|
||||
state_string = StringUtil::Ellipsise(Achievements::GetRichPresenceString(), 128);
|
||||
rp.state = state_string.c_str();
|
||||
}
|
||||
|
||||
Discord_UpdatePresence(&rp);
|
||||
Discord_RunCallbacks();
|
||||
|
@ -3020,4 +3019,3 @@ void VMManager::PollDiscordPresence()
|
|||
|
||||
Discord_RunCallbacks();
|
||||
}
|
||||
|
||||
|
|
|
@ -102,6 +102,9 @@ namespace VMManager
|
|||
/// Runs the VM until the CPU execution is canceled.
|
||||
void Execute();
|
||||
|
||||
/// Polls input, updates subsystems which are present while paused/inactive.
|
||||
void IdlePollUpdate();
|
||||
|
||||
/// Changes the pause state of the VM, resetting anything needed when unpausing.
|
||||
void SetPaused(bool paused);
|
||||
|
||||
|
@ -215,7 +218,7 @@ namespace VMManager
|
|||
u64 GetSessionPlayedTime();
|
||||
|
||||
/// Called when the rich presence string, provided by RetroAchievements, changes.
|
||||
void UpdateDiscordPresence(const std::string& rich_presence);
|
||||
void UpdateDiscordPresence();
|
||||
|
||||
/// Internal callbacks, implemented in the emu core.
|
||||
namespace Internal
|
||||
|
|
|
@ -214,10 +214,18 @@ void Host::OnAchievementsLoginRequested(Achievements::LoginRequestReason reason)
|
|||
{
|
||||
}
|
||||
|
||||
void Host::OnAchievementsLoginSuccess(const char* username, u32 points, u32 sc_points, u32 unread_messages)
|
||||
{
|
||||
}
|
||||
|
||||
void Host::OnAchievementsRefreshed()
|
||||
{
|
||||
}
|
||||
|
||||
void Host::OnAchievementsHardcoreModeChanged(bool enabled)
|
||||
{
|
||||
}
|
||||
|
||||
std::optional<u32> InputManager::ConvertHostKeyboardStringToCode(const std::string_view& str)
|
||||
{
|
||||
return std::nullopt;
|
||||
|
|
Loading…
Reference in New Issue