22 changes: 21 additions & 1 deletion Source/Core/Core/AchievementManager.h
Expand Up @@ -56,6 +56,13 @@ class AchievementManager
using FormattedValue = std::array<char, FORMAT_SIZE>;
static constexpr size_t RP_SIZE = 256;
using RichPresence = std::array<char, RP_SIZE>;
using Badge = std::vector<u8>;

struct BadgeStatus
{
std::string name = "";
Badge badge{};
};

struct UnlockStatus
{
Expand All @@ -68,8 +75,14 @@ class AchievementManager
} remote_unlock_status = UnlockType::LOCKED;
u32 session_unlock_count = 0;
u32 points = 0;
BadgeStatus locked_badge;
BadgeStatus unlocked_badge;
};

static constexpr std::string_view GRAY = "transparent";
static constexpr std::string_view GOLD = "#FFD700";
static constexpr std::string_view BLUE = "#0B71C1";

static AchievementManager* GetInstance();
void Init();
void SetUpdateCallback(UpdateCallback callback);
Expand All @@ -83,6 +96,7 @@ class AchievementManager
void ActivateDeactivateAchievements();
void ActivateDeactivateLeaderboards();
void ActivateDeactivateRichPresence();
void FetchBadges();

void DoFrame();
u32 MemoryPeeker(u32 address, u32 num_bytes, void* ud);
Expand All @@ -91,10 +105,12 @@ class AchievementManager
std::recursive_mutex* GetLock();
std::string GetPlayerDisplayName() const;
u32 GetPlayerScore() const;
const BadgeStatus& GetPlayerBadge() const;
std::string GetGameDisplayName() const;
PointSpread TallyScore() const;
rc_api_fetch_game_data_response_t* GetGameData();
UnlockStatus GetUnlockStatus(AchievementId achievement_id) const;
const BadgeStatus& GetGameBadge() const;
const UnlockStatus& GetUnlockStatus(AchievementId achievement_id) const;
void GetAchievementProgress(AchievementId achievement_id, u32* value, u32* target);
RichPresence GetRichPresence();

Expand Down Expand Up @@ -129,23 +145,27 @@ class AchievementManager
ResponseType Request(RcRequest rc_request, RcResponse* rc_response,
const std::function<int(rc_api_request_t*, const RcRequest*)>& init_request,
const std::function<int(RcResponse*, const char*)>& process_response);
ResponseType RequestImage(rc_api_fetch_image_request_t rc_request, Badge* rc_response);

rc_runtime_t m_runtime{};
Core::System* m_system{};
bool m_is_runtime_initialized = false;
UpdateCallback m_update_callback;
std::string m_display_name;
u32 m_player_score = 0;
BadgeStatus m_player_badge;
std::array<char, HASH_LENGTH> m_game_hash{};
u32 m_game_id = 0;
rc_api_fetch_game_data_response_t m_game_data{};
bool m_is_game_loaded = false;
BadgeStatus m_game_badge;
RichPresence m_rich_presence;
time_t m_last_ping_time = 0;

std::unordered_map<AchievementId, UnlockStatus> m_unlock_map;

Common::WorkQueueThread<std::function<void()>> m_queue;
Common::WorkQueueThread<std::function<void()>> m_image_queue;
std::recursive_mutex m_lock;
}; // class AchievementManager

Expand Down
1 change: 1 addition & 0 deletions Source/Core/Core/Config/AchievementSettings.cpp
Expand Up @@ -19,6 +19,7 @@ const Info<bool> RA_LEADERBOARDS_ENABLED{
{System::Achievements, "Achievements", "LeaderboardsEnabled"}, false};
const Info<bool> RA_RICH_PRESENCE_ENABLED{
{System::Achievements, "Achievements", "RichPresenceEnabled"}, false};
const Info<bool> RA_BADGES_ENABLED{{System::Achievements, "Achievements", "BadgesEnabled"}, false};
const Info<bool> RA_UNOFFICIAL_ENABLED{{System::Achievements, "Achievements", "UnofficialEnabled"},
false};
const Info<bool> RA_ENCORE_ENABLED{{System::Achievements, "Achievements", "EncoreEnabled"}, false};
Expand Down
1 change: 1 addition & 0 deletions Source/Core/Core/Config/AchievementSettings.h
Expand Up @@ -14,6 +14,7 @@ extern const Info<std::string> RA_API_TOKEN;
extern const Info<bool> RA_ACHIEVEMENTS_ENABLED;
extern const Info<bool> RA_LEADERBOARDS_ENABLED;
extern const Info<bool> RA_RICH_PRESENCE_ENABLED;
extern const Info<bool> RA_BADGES_ENABLED;
extern const Info<bool> RA_UNOFFICIAL_ENABLED;
extern const Info<bool> RA_ENCORE_ENABLED;
} // namespace Config
1 change: 1 addition & 0 deletions Source/Core/Core/ConfigLoaders/IsSettingSaveable.cpp
Expand Up @@ -47,6 +47,7 @@ bool IsSettingSaveable(const Config::Location& config_location)
&Config::RA_ACHIEVEMENTS_ENABLED.GetLocation(),
&Config::RA_LEADERBOARDS_ENABLED.GetLocation(),
&Config::RA_RICH_PRESENCE_ENABLED.GetLocation(),
&Config::RA_BADGES_ENABLED.GetLocation(),
&Config::RA_UNOFFICIAL_ENABLED.GetLocation(),
&Config::RA_ENCORE_ENABLED.GetLocation(),
};
Expand Down
152 changes: 101 additions & 51 deletions Source/Core/DolphinQt/Achievements/AchievementHeaderWidget.cpp
Expand Up @@ -30,84 +30,134 @@

AchievementHeaderWidget::AchievementHeaderWidget(QWidget* parent) : QWidget(parent)
{
m_user_name = new QLabel();
m_user_points = new QLabel();
m_game_name = new QLabel();
m_game_points = new QLabel();
m_user_icon = new QLabel();
m_game_icon = new QLabel();
m_name = new QLabel();
m_points = new QLabel();
m_game_progress_hard = new QProgressBar();
m_game_progress_soft = new QProgressBar();
m_rich_presence = new QLabel();

QVBoxLayout* m_user_right_col = new QVBoxLayout();
m_user_right_col->addWidget(m_user_name);
m_user_right_col->addWidget(m_user_points);
QHBoxLayout* m_user_layout = new QHBoxLayout();
// TODO: player badge goes here
m_user_layout->addLayout(m_user_right_col);
m_user_box = new QGroupBox();
m_user_box->setLayout(m_user_layout);

QVBoxLayout* m_game_right_col = new QVBoxLayout();
m_game_right_col->addWidget(m_game_name);
m_game_right_col->addWidget(m_game_points);
m_game_right_col->addWidget(m_game_progress_hard);
m_game_right_col->addWidget(m_game_progress_soft);
QHBoxLayout* m_game_upper_row = new QHBoxLayout();
// TODO: player badge and game badge go here
m_game_upper_row->addLayout(m_game_right_col);
QVBoxLayout* m_game_layout = new QVBoxLayout();
m_game_layout->addLayout(m_game_upper_row);
m_game_layout->addWidget(m_rich_presence);
m_game_box = new QGroupBox();
m_game_box->setLayout(m_game_layout);
QSizePolicy sp_retain = m_game_progress_hard->sizePolicy();
sp_retain.setRetainSizeWhenHidden(true);
m_game_progress_hard->setSizePolicy(sp_retain);
sp_retain = m_game_progress_soft->sizePolicy();
sp_retain.setRetainSizeWhenHidden(true);
m_game_progress_soft->setSizePolicy(sp_retain);

QVBoxLayout* icon_col = new QVBoxLayout();
icon_col->addWidget(m_user_icon);
icon_col->addWidget(m_game_icon);
QVBoxLayout* text_col = new QVBoxLayout();
text_col->addWidget(m_name);
text_col->addWidget(m_points);
text_col->addWidget(m_game_progress_hard);
text_col->addWidget(m_game_progress_soft);
text_col->addWidget(m_rich_presence);
QHBoxLayout* header_layout = new QHBoxLayout();
header_layout->addLayout(icon_col);
header_layout->addLayout(text_col);
m_header_box = new QGroupBox();
m_header_box->setLayout(header_layout);

QVBoxLayout* m_total = new QVBoxLayout();
m_total->addWidget(m_user_box);
m_total->addWidget(m_game_box);
m_total->addWidget(m_header_box);

m_total->setContentsMargins(0, 0, 0, 0);
m_total->setAlignment(Qt::AlignTop);
setLayout(m_total);

std::lock_guard lg{*AchievementManager::GetInstance()->GetLock()};
UpdateData();
}

void AchievementHeaderWidget::UpdateData()
{
if (!AchievementManager::GetInstance()->IsLoggedIn())
{
m_user_box->setVisible(false);
m_game_box->setVisible(false);
m_header_box->setVisible(false);
return;
}

AchievementManager::PointSpread point_spread = AchievementManager::GetInstance()->TallyScore();
QString user_name =
QString::fromStdString(AchievementManager::GetInstance()->GetPlayerDisplayName());
m_user_name->setText(user_name);
m_user_points->setText(tr("%1 points").arg(AchievementManager::GetInstance()->GetPlayerScore()));
QString game_name =
QString::fromStdString(AchievementManager::GetInstance()->GetGameDisplayName());
AchievementManager::BadgeStatus player_badge =
AchievementManager::GetInstance()->GetPlayerBadge();
AchievementManager::BadgeStatus game_badge = AchievementManager::GetInstance()->GetGameBadge();

m_user_icon->setVisible(false);
m_user_icon->clear();
m_user_icon->setText({});
if (Config::Get(Config::RA_BADGES_ENABLED))
{
if (player_badge.name != "")
{
QImage i_user_icon{};
if (i_user_icon.loadFromData(&player_badge.badge.front(), (int)player_badge.badge.size()))
{
m_user_icon->setPixmap(QPixmap::fromImage(i_user_icon)
.scaled(64, 64, Qt::KeepAspectRatio, Qt::SmoothTransformation));
m_user_icon->adjustSize();
m_user_icon->setStyleSheet(QStringLiteral("border: 4px solid transparent"));
m_user_icon->setVisible(true);
}
}
}
m_game_icon->setVisible(false);
m_game_icon->clear();
m_game_icon->setText({});
if (Config::Get(Config::RA_BADGES_ENABLED))
{
if (game_badge.name != "")
{
QImage i_game_icon{};
if (i_game_icon.loadFromData(&game_badge.badge.front(), (int)game_badge.badge.size()))
{
m_game_icon->setPixmap(QPixmap::fromImage(i_game_icon)
.scaled(64, 64, Qt::KeepAspectRatio, Qt::SmoothTransformation));
m_game_icon->adjustSize();
std::string_view color = AchievementManager::GRAY;
if (point_spread.hard_unlocks == point_spread.total_count)
color = AchievementManager::GOLD;
else if (point_spread.hard_unlocks + point_spread.soft_unlocks == point_spread.total_count)
color = AchievementManager::BLUE;
m_game_icon->setStyleSheet(
QStringLiteral("border: 4px solid %1").arg(QString::fromStdString(std::string(color))));
m_game_icon->setVisible(true);
}
}
}

if (!AchievementManager::GetInstance()->IsGameLoaded())
if (!game_name.isEmpty())
{
m_user_box->setVisible(true);
m_game_box->setVisible(false);
return;
m_name->setText(tr("%1 is playing %2").arg(user_name).arg(game_name));
m_points->setText(GetPointsString(user_name, point_spread));

m_game_progress_hard->setRange(0, point_spread.total_count);
if (!m_game_progress_hard->isVisible())
m_game_progress_hard->setVisible(true);
m_game_progress_soft->setValue(point_spread.hard_unlocks);
m_game_progress_soft->setRange(0, point_spread.total_count);
m_game_progress_soft->setValue(point_spread.hard_unlocks + point_spread.soft_unlocks);
if (!m_game_progress_soft->isVisible())
m_game_progress_soft->setVisible(true);
m_rich_presence->setText(
QString::fromUtf8(AchievementManager::GetInstance()->GetRichPresence().data()));
if (!m_rich_presence->isVisible())
m_rich_presence->setVisible(Config::Get(Config::RA_RICH_PRESENCE_ENABLED));
}
else
{
m_name->setText(user_name);
m_points->setText(tr("%1 points").arg(AchievementManager::GetInstance()->GetPlayerScore()));

AchievementManager::PointSpread point_spread = AchievementManager::GetInstance()->TallyScore();
m_game_name->setText(
QString::fromStdString(AchievementManager::GetInstance()->GetGameDisplayName()));
m_game_points->setText(GetPointsString(user_name, point_spread));
m_game_progress_hard = new QProgressBar();
m_game_progress_hard->setRange(0, point_spread.total_count);
m_game_progress_soft->setValue(point_spread.hard_unlocks);
m_game_progress_soft->setRange(0, point_spread.total_count);
m_game_progress_soft->setValue(point_spread.hard_unlocks + point_spread.soft_unlocks);
m_rich_presence->setText(
QString::fromUtf8(AchievementManager::GetInstance()->GetRichPresence().data()));
m_rich_presence->setVisible(Config::Get(Config::RA_RICH_PRESENCE_ENABLED));

m_user_box->setVisible(false);
m_game_box->setVisible(true);
m_game_progress_hard->setVisible(false);
m_game_progress_soft->setVisible(false);
m_rich_presence->setVisible(false);
}
}

QString
Expand Down
12 changes: 5 additions & 7 deletions Source/Core/DolphinQt/Achievements/AchievementHeaderWidget.h
Expand Up @@ -27,16 +27,14 @@ class AchievementHeaderWidget final : public QWidget
QGroupBox* m_common_box;
QVBoxLayout* m_common_layout;

QLabel* m_user_name;
QLabel* m_user_points;
QLabel* m_game_name;
QLabel* m_game_points;
QLabel* m_user_icon;
QLabel* m_game_icon;
QLabel* m_name;
QLabel* m_points;
QProgressBar* m_game_progress_hard;
QProgressBar* m_game_progress_soft;
QLabel* m_rich_presence;

QGroupBox* m_user_box;
QGroupBox* m_game_box;
QGroupBox* m_header_box;
};

#endif // USE_RETRO_ACHIEVEMENTS
50 changes: 48 additions & 2 deletions Source/Core/DolphinQt/Achievements/AchievementProgressWidget.cpp
Expand Up @@ -30,12 +30,17 @@
#include "DolphinQt/QtUtils/SignalBlocking.h"
#include "DolphinQt/Settings.h"

static constexpr bool hardcore_mode_enabled = false;

AchievementProgressWidget::AchievementProgressWidget(QWidget* parent) : QWidget(parent)
{
m_common_box = new QGroupBox();
m_common_layout = new QVBoxLayout();

UpdateData();
{
std::lock_guard lg{*AchievementManager::GetInstance()->GetLock()};
UpdateData();
}

m_common_box->setLayout(m_common_layout);

Expand All @@ -51,12 +56,53 @@ AchievementProgressWidget::CreateAchievementBox(const rc_api_achievement_definit
{
if (!AchievementManager::GetInstance()->IsGameLoaded())
return new QGroupBox();
QLabel* a_badge = new QLabel();
const auto unlock_status = AchievementManager::GetInstance()->GetUnlockStatus(achievement->id);
const AchievementManager::BadgeStatus* badge = &unlock_status.locked_badge;
std::string_view color = AchievementManager::GRAY;
if (unlock_status.remote_unlock_status == AchievementManager::UnlockStatus::UnlockType::HARDCORE)
{
badge = &unlock_status.unlocked_badge;
color = AchievementManager::GOLD;
}
else if (hardcore_mode_enabled && unlock_status.session_unlock_count > 1)
{
badge = &unlock_status.unlocked_badge;
color = AchievementManager::GOLD;
}
else if (unlock_status.remote_unlock_status ==
AchievementManager::UnlockStatus::UnlockType::SOFTCORE)
{
badge = &unlock_status.unlocked_badge;
color = AchievementManager::BLUE;
}
else if (unlock_status.session_unlock_count > 1)
{
badge = &unlock_status.unlocked_badge;
color = AchievementManager::BLUE;
}
if (Config::Get(Config::RA_BADGES_ENABLED) && badge->name != "")
{
QImage i_badge{};
if (i_badge.loadFromData(&badge->badge.front(), (int)badge->badge.size()))
{
a_badge->setPixmap(QPixmap::fromImage(i_badge).scaled(64, 64, Qt::KeepAspectRatio,
Qt::SmoothTransformation));
a_badge->adjustSize();
a_badge->setStyleSheet(
QStringLiteral("border: 4px solid %1").arg(QString::fromStdString(std::string(color))));
}
}

QLabel* a_title = new QLabel(QString::fromUtf8(achievement->title, strlen(achievement->title)));
QLabel* a_description =
new QLabel(QString::fromUtf8(achievement->description, strlen(achievement->description)));
QLabel* a_points = new QLabel(tr("%1 points").arg(achievement->points));
QLabel* a_status = new QLabel(GetStatusString(achievement->id));
QProgressBar* a_progress_bar = new QProgressBar();
QSizePolicy sp_retain = a_progress_bar->sizePolicy();
sp_retain.setRetainSizeWhenHidden(true);
a_progress_bar->setSizePolicy(sp_retain);
unsigned int value = 0;
unsigned int target = 0;
AchievementManager::GetInstance()->GetAchievementProgress(achievement->id, &value, &target);
Expand All @@ -77,7 +123,7 @@ AchievementProgressWidget::CreateAchievementBox(const rc_api_achievement_definit
a_col_right->addWidget(a_status);
a_col_right->addWidget(a_progress_bar);
QHBoxLayout* a_total = new QHBoxLayout();
// TODO: achievement badge goes here
a_total->addWidget(a_badge);
a_total->addLayout(a_col_right);
QGroupBox* a_group_box = new QGroupBox();
a_group_box->setLayout(a_total);
Expand Down
18 changes: 18 additions & 0 deletions Source/Core/DolphinQt/Achievements/AchievementSettingsWidget.cpp
Expand Up @@ -75,6 +75,11 @@ void AchievementSettingsWidget::CreateLayout()
"achievements.<br><br>Unofficial achievements may be optional or unfinished achievements "
"that have not been deemed official by RetroAchievements and may be useful for testing or "
"simply for fun."));
m_common_badges_enabled_input = new ToolTipCheckBox(tr("Enable Achievement Badges"));
m_common_badges_enabled_input->SetDescription(
tr("Enable achievement badges.<br><br>Displays icons for the player, game, and achievements. "
"Simple visual option, but will require a small amount of extra memory and time to "
"download the images."));
m_common_encore_enabled_input = new ToolTipCheckBox(tr("Enable Encore Achievements"));
m_common_encore_enabled_input->SetDescription(tr(
"Enable unlocking achievements in Encore Mode.<br><br>Encore Mode re-enables achievements "
Expand All @@ -92,6 +97,7 @@ void AchievementSettingsWidget::CreateLayout()
m_common_layout->addWidget(m_common_achievements_enabled_input);
m_common_layout->addWidget(m_common_leaderboards_enabled_input);
m_common_layout->addWidget(m_common_rich_presence_enabled_input);
m_common_layout->addWidget(m_common_badges_enabled_input);
m_common_layout->addWidget(m_common_unofficial_enabled_input);
m_common_layout->addWidget(m_common_encore_enabled_input);

Expand All @@ -111,6 +117,8 @@ void AchievementSettingsWidget::ConnectWidgets()
&AchievementSettingsWidget::ToggleLeaderboards);
connect(m_common_rich_presence_enabled_input, &QCheckBox::toggled, this,
&AchievementSettingsWidget::ToggleRichPresence);
connect(m_common_badges_enabled_input, &QCheckBox::toggled, this,
&AchievementSettingsWidget::ToggleBadges);
connect(m_common_unofficial_enabled_input, &QCheckBox::toggled, this,
&AchievementSettingsWidget::ToggleUnofficial);
connect(m_common_encore_enabled_input, &QCheckBox::toggled, this,
Expand Down Expand Up @@ -157,6 +165,9 @@ void AchievementSettingsWidget::LoadSettings()
->setChecked(Config::Get(Config::RA_RICH_PRESENCE_ENABLED));
SignalBlocking(m_common_rich_presence_enabled_input)->setEnabled(enabled);

SignalBlocking(m_common_badges_enabled_input)->setChecked(Config::Get(Config::RA_BADGES_ENABLED));
SignalBlocking(m_common_badges_enabled_input)->setEnabled(enabled);

SignalBlocking(m_common_unofficial_enabled_input)
->setChecked(Config::Get(Config::RA_UNOFFICIAL_ENABLED));
SignalBlocking(m_common_unofficial_enabled_input)->setEnabled(enabled && achievements_enabled);
Expand All @@ -176,6 +187,7 @@ void AchievementSettingsWidget::SaveSettings()
m_common_leaderboards_enabled_input->isChecked());
Config::SetBaseOrCurrent(Config::RA_RICH_PRESENCE_ENABLED,
m_common_rich_presence_enabled_input->isChecked());
Config::SetBaseOrCurrent(Config::RA_BADGES_ENABLED, m_common_badges_enabled_input->isChecked());
Config::SetBaseOrCurrent(Config::RA_UNOFFICIAL_ENABLED,
m_common_unofficial_enabled_input->isChecked());
Config::SetBaseOrCurrent(Config::RA_ENCORE_ENABLED, m_common_encore_enabled_input->isChecked());
Expand Down Expand Up @@ -224,6 +236,12 @@ void AchievementSettingsWidget::ToggleRichPresence()
AchievementManager::GetInstance()->ActivateDeactivateRichPresence();
}

void AchievementSettingsWidget::ToggleBadges()
{
SaveSettings();
AchievementManager::GetInstance()->FetchBadges();
}

void AchievementSettingsWidget::ToggleUnofficial()
{
SaveSettings();
Expand Down
Expand Up @@ -36,7 +36,7 @@ class AchievementSettingsWidget final : public QWidget
void ToggleLeaderboards();
void ToggleRichPresence();
void ToggleHardcore();
void ToggleBadgeIcons();
void ToggleBadges();
void ToggleUnofficial();
void ToggleEncore();

Expand All @@ -55,6 +55,7 @@ class AchievementSettingsWidget final : public QWidget
ToolTipCheckBox* m_common_achievements_enabled_input;
ToolTipCheckBox* m_common_leaderboards_enabled_input;
ToolTipCheckBox* m_common_rich_presence_enabled_input;
ToolTipCheckBox* m_common_badges_enabled_input;
ToolTipCheckBox* m_common_unofficial_enabled_input;
ToolTipCheckBox* m_common_encore_enabled_input;
};
Expand Down