Update Achievement Dialog Header to use rc_client

Two portions of this need updating.
Anything related to points and unlock counts and scoring uses game_summary now instead of the TallyScore method. Unfortunately this comes with the drawback that I cannot easily at this time access the number of points/unlocks from the other hardcore mode, so things like the second progress bar have been deleted.
Rich presence, which no longer needs to be stored, but can be calculated at request. As the AchievementHeader can now update just the Rich Presence, DoFrame can now simply call a header update with .rp=true and the current Rich Presence will be calculated immediately.
As the two items above are the last remaining things to use a number of the components in AchievementManager, this also deletes: Request V1 (V2 is renamed accordingly), ResponseType, PointSpread, TallyScore, UnlockStatus, and the RP generation and ping methods.
This commit is contained in:
LillyJadeKatrin 2024-04-05 22:46:40 -04:00
parent 4214c301ef
commit 878e6e847b
4 changed files with 52 additions and 273 deletions

View File

@ -36,7 +36,7 @@ void AchievementManager::Init()
{
if (!m_client && Config::Get(Config::RA_ENABLED))
{
m_client = rc_client_create(MemoryPeeker, RequestV2);
m_client = rc_client_create(MemoryPeeker, Request);
std::string host_url = Config::Get(Config::RA_HOST_URL);
if (!host_url.empty())
rc_client_set_host(m_client, host_url.c_str());
@ -226,12 +226,11 @@ void AchievementManager::DoFrame()
}
if (!m_system)
return;
time_t current_time = std::time(nullptr);
if (difftime(current_time, m_last_ping_time) > 120)
auto current_time = std::chrono::steady_clock::now();
if (current_time - m_last_rp_time > std::chrono::seconds{10})
{
GenerateRichPresence(Core::CPUThreadGuard{*m_system});
m_queue.EmplaceItem([this] { PingRichPresence(m_rich_presence); });
m_last_ping_time = current_time;
m_last_rp_time = current_time;
rc_client_get_rich_presence_message(m_client, m_rich_presence.data(), RP_SIZE);
m_update_callback(UpdatedItems{.rich_presence = true});
}
}
@ -283,35 +282,6 @@ std::string_view AchievementManager::GetGameDisplayName() const
return IsGameLoaded() ? std::string_view(rc_client_get_game_info(m_client)->title) : "";
}
AchievementManager::PointSpread AchievementManager::TallyScore() const
{
PointSpread spread{};
if (!IsGameLoaded())
return spread;
bool hardcore_mode_enabled = Config::Get(Config::RA_HARDCORE_ENABLED);
for (const auto& entry : m_unlock_map)
{
if (entry.second.category != RC_ACHIEVEMENT_CATEGORY_CORE)
continue;
u32 points = entry.second.points;
spread.total_count++;
spread.total_points += points;
if (entry.second.remote_unlock_status == UnlockStatus::UnlockType::HARDCORE ||
(hardcore_mode_enabled && entry.second.session_unlock_count > 0))
{
spread.hard_unlocks++;
spread.hard_points += points;
}
else if (entry.second.remote_unlock_status == UnlockStatus::UnlockType::SOFTCORE ||
entry.second.session_unlock_count > 0)
{
spread.soft_unlocks++;
spread.soft_points += points;
}
}
return spread;
}
rc_client_t* AchievementManager::GetClient()
{
return m_client;
@ -335,35 +305,6 @@ const AchievementManager::BadgeStatus& AchievementManager::GetAchievementBadge(A
return (itr == badge_list.end()) ? m_default_badge : itr->second;
}
const AchievementManager::UnlockStatus*
AchievementManager::GetUnlockStatus(AchievementId achievement_id) const
{
if (m_unlock_map.count(achievement_id) < 1)
return nullptr;
return &m_unlock_map.at(achievement_id);
}
AchievementManager::ResponseType
AchievementManager::GetAchievementProgress(AchievementId achievement_id, u32* value, u32* target)
{
if (!IsGameLoaded())
{
ERROR_LOG_FMT(
ACHIEVEMENTS,
"Attempted to request measured data for achievement ID {} when no game is running.",
achievement_id);
return ResponseType::INVALID_REQUEST;
}
int result = rc_runtime_get_achievement_measured(&m_runtime, achievement_id, value, target);
if (result == 0)
{
WARN_LOG_FMT(ACHIEVEMENTS, "Failed to get measured data for achievement ID {}.",
achievement_id);
return ResponseType::MALFORMED_OBJECT;
}
return ResponseType::SUCCESS;
}
const AchievementManager::LeaderboardStatus*
AchievementManager::GetLeaderboardInfo(AchievementManager::AchievementId leaderboard_id)
{
@ -380,7 +321,6 @@ AchievementManager::GetLeaderboardInfo(AchievementManager::AchievementId leaderb
AchievementManager::RichPresence AchievementManager::GetRichPresence() const
{
std::lock_guard lg{m_lock};
return m_rich_presence;
}
@ -476,7 +416,6 @@ void AchievementManager::CloseGame()
m_game_badge.name.clear();
m_unlocked_badges.clear();
m_locked_badges.clear();
m_unlock_map.clear();
m_leaderboard_map.clear();
rc_api_destroy_fetch_game_data_response(&m_game_data);
m_game_data = {};
@ -664,30 +603,6 @@ void AchievementManager::LeaderboardEntriesCallback(int result, const char* erro
AchievementManager::GetInstance().m_update_callback({.leaderboards = {leaderboard_id}});
}
void AchievementManager::GenerateRichPresence(const Core::CPUThreadGuard& guard)
{
std::lock_guard lg{m_lock};
rc_runtime_get_richpresence(
&m_runtime, m_rich_presence.data(), RP_SIZE,
[](unsigned address, unsigned num_bytes, void* ud) { return 0u; }, this, nullptr);
}
AchievementManager::ResponseType
AchievementManager::PingRichPresence(const RichPresence& rich_presence)
{
std::string username = Config::Get(Config::RA_USERNAME);
std::string api_token = Config::Get(Config::RA_API_TOKEN);
rc_api_ping_request_t ping_request = {.username = username.c_str(),
.api_token = api_token.c_str(),
.game_id = m_game_id,
.rich_presence = rich_presence.data()};
rc_api_ping_response_t ping_response = {};
ResponseType r_type = Request<rc_api_ping_request_t, rc_api_ping_response_t>(
ping_request, &ping_response, rc_api_init_ping_request, rc_api_process_ping_response);
rc_api_destroy_ping_response(&ping_response);
return r_type;
}
void AchievementManager::LoadGameCallback(int result, const char* error_message,
rc_client_t* client, void* userdata)
{
@ -708,6 +623,9 @@ void AchievementManager::LoadGameCallback(int result, const char* error_message,
AchievementManager::GetInstance().FetchGameBadges();
AchievementManager::GetInstance().m_system = &Core::System::GetInstance();
AchievementManager::GetInstance().m_update_callback({.all = true});
// Set this to a value that will immediately trigger RP
AchievementManager::GetInstance().m_last_rp_time =
std::chrono::steady_clock::now() - std::chrono::minutes{2};
}
void AchievementManager::DisplayWelcomeMessage()
@ -867,57 +785,6 @@ void AchievementManager::HandleGameCompletedEvent(const rc_client_event_t* clien
nullptr);
}
// Every RetroAchievements API call, with only a partial exception for fetch_image, follows
// the same design pattern (here, X is the name of the call):
// Create a specific rc_api_X_request_t struct and populate with the necessary values
// Call rc_api_init_X_request to convert this into a generic rc_api_request_t struct
// Perform the HTTP request using the url and post_data in the rc_api_request_t struct
// Call rc_api_process_X_response to convert the raw string HTTP response into a
// rc_api_X_response_t struct
// Use the data in the rc_api_X_response_t struct as needed
// Call rc_api_destroy_X_response when finished with the response struct to free memory
template <typename RcRequest, typename RcResponse>
AchievementManager::ResponseType AchievementManager::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)
{
rc_api_request_t api_request;
Common::HttpRequest http_request;
if (init_request(&api_request, &rc_request) != RC_OK || !api_request.post_data)
{
ERROR_LOG_FMT(ACHIEVEMENTS, "Invalid API request.");
return ResponseType::INVALID_REQUEST;
}
auto http_response = http_request.Post(api_request.url, api_request.post_data);
rc_api_destroy_request(&api_request);
if (http_response.has_value() && http_response->size() > 0)
{
const std::string response_str(http_response->begin(), http_response->end());
if (process_response(rc_response, response_str.c_str()) != RC_OK)
{
ERROR_LOG_FMT(ACHIEVEMENTS, "Failed to process HTTP response. \nURL: {} \nresponse: {}",
api_request.url, response_str);
return ResponseType::MALFORMED_OBJECT;
}
if (rc_response->response.succeeded)
{
return ResponseType::SUCCESS;
}
else
{
Logout();
WARN_LOG_FMT(ACHIEVEMENTS, "Invalid RetroAchievements credentials; failed login.");
return ResponseType::INVALID_CREDENTIALS;
}
}
else
{
WARN_LOG_FMT(ACHIEVEMENTS, "RetroAchievements connection failed. \nURL: {}", api_request.url);
return ResponseType::CONNECTION_FAILED;
}
}
static std::unique_ptr<OSD::Icon> DecodeBadgeToOSDIcon(const AchievementManager::Badge& badge)
{
if (badge.empty())
@ -932,7 +799,7 @@ static std::unique_ptr<OSD::Icon> DecodeBadgeToOSDIcon(const AchievementManager:
return icon;
}
void AchievementManager::RequestV2(const rc_api_request_t* request,
void AchievementManager::Request(const rc_api_request_t* request,
rc_client_server_callback_t callback, void* callback_data,
rc_client_t* client)
{

View File

@ -37,31 +37,8 @@ struct Icon;
class AchievementManager
{
public:
enum class ResponseType
{
SUCCESS,
NOT_ENABLED,
MANAGER_NOT_INITIALIZED,
INVALID_REQUEST,
INVALID_CREDENTIALS,
CONNECTION_FAILED,
MALFORMED_OBJECT,
EXPIRED_CONTEXT,
UNKNOWN_FAILURE
};
using ResponseCallback = std::function<void(ResponseType)>;
using BadgeNameFunction = std::function<std::string(const AchievementManager&)>;
struct PointSpread
{
u32 total_count;
u32 total_points;
u32 hard_unlocks;
u32 hard_points;
u32 soft_unlocks;
u32 soft_points;
};
static constexpr size_t HASH_SIZE = 33;
using Hash = std::array<char, HASH_SIZE>;
using AchievementId = u32;
@ -80,22 +57,6 @@ public:
Badge badge{};
};
struct UnlockStatus
{
AchievementId game_data_index = 0;
enum class UnlockType
{
LOCKED,
SOFTCORE,
HARDCORE
} remote_unlock_status = UnlockType::LOCKED;
u32 session_unlock_count = 0;
u32 points = 0;
BadgeStatus locked_badge;
BadgeStatus unlocked_badge;
u32 category = RC_ACHIEVEMENT_CATEGORY_CORE;
};
static constexpr std::string_view GRAY = "transparent";
static constexpr std::string_view GOLD = "#FFD700";
static constexpr std::string_view BLUE = "#0B71C1";
@ -147,14 +108,10 @@ public:
u32 GetPlayerScore() const;
const BadgeStatus& GetPlayerBadge() const;
std::string_view GetGameDisplayName() const;
PointSpread TallyScore() const;
rc_client_t* GetClient();
rc_api_fetch_game_data_response_t* GetGameData();
const BadgeStatus& GetGameBadge() const;
const BadgeStatus& GetAchievementBadge(AchievementId id, bool locked) const;
const UnlockStatus* GetUnlockStatus(AchievementId achievement_id) const;
AchievementManager::ResponseType GetAchievementProgress(AchievementId achievement_id, u32* value,
u32* target);
const LeaderboardStatus* GetLeaderboardInfo(AchievementId leaderboard_id);
RichPresence GetRichPresence() const;
bool IsDisabled() const { return m_disabled; };
@ -193,10 +150,6 @@ private:
std::unique_ptr<DiscIO::Volume>& GetLoadingVolume() { return m_loading_volume; };
void GenerateRichPresence(const Core::CPUThreadGuard& guard);
ResponseType PingRichPresence(const RichPresence& rich_presence);
static void LoadGameCallback(int result, const char* error_message, rc_client_t* client,
void* userdata);
void DisplayWelcomeMessage();
@ -217,12 +170,7 @@ private:
static void HandleAchievementProgressIndicatorShowEvent(const rc_client_event_t* client_event);
static void HandleGameCompletedEvent(const rc_client_event_t* client_event, rc_client_t* client);
template <typename RcRequest, typename RcResponse>
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);
static void RequestV2(const rc_api_request_t* request, rc_client_server_callback_t callback,
static void Request(const rc_api_request_t* request, rc_client_server_callback_t callback,
void* callback_data, rc_client_t* client);
static u32 MemoryPeeker(u32 address, u8* buffer, u32 num_bytes, rc_client_t* client);
void FetchBadge(BadgeStatus* badge, u32 badge_type, const BadgeNameFunction function,
@ -246,9 +194,8 @@ private:
std::unordered_map<AchievementId, BadgeStatus> m_unlocked_badges;
std::unordered_map<AchievementId, BadgeStatus> m_locked_badges;
RichPresence m_rich_presence;
time_t m_last_ping_time = 0;
std::chrono::steady_clock::time_point m_last_rp_time = std::chrono::steady_clock::now();
std::unordered_map<AchievementId, UnlockStatus> m_unlock_map;
std::unordered_map<AchievementId, LeaderboardStatus> m_leaderboard_map;
NamedIconMap m_active_challenges;
std::vector<rc_client_leaderboard_tracker_t> m_active_leaderboards;

View File

@ -11,6 +11,8 @@
#include <QString>
#include <QVBoxLayout>
#include <rcheevos/include/rc_client.h>
#include "Core/AchievementManager.h"
#include "Core/Config/AchievementSettings.h"
#include "Core/Core.h"
@ -24,8 +26,7 @@ AchievementHeaderWidget::AchievementHeaderWidget(QWidget* parent) : QWidget(pare
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_game_progress = new QProgressBar();
m_rich_presence = new QLabel();
m_locked_warning = new QLabel();
@ -33,12 +34,9 @@ AchievementHeaderWidget::AchievementHeaderWidget(QWidget* parent) : QWidget(pare
"games to re-enable achievements."));
m_locked_warning->setStyleSheet(QStringLiteral("QLabel { color : red; }"));
QSizePolicy sp_retain = m_game_progress_hard->sizePolicy();
QSizePolicy sp_retain = m_game_progress->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);
m_game_progress->setSizePolicy(sp_retain);
QVBoxLayout* icon_col = new QVBoxLayout();
icon_col->addWidget(m_user_icon);
@ -46,8 +44,7 @@ AchievementHeaderWidget::AchievementHeaderWidget(QWidget* parent) : QWidget(pare
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_game_progress);
text_col->addWidget(m_rich_presence);
text_col->addWidget(m_locked_warning);
QHBoxLayout* header_layout = new QHBoxLayout();
@ -74,7 +71,6 @@ void AchievementHeaderWidget::UpdateData()
return;
}
AchievementManager::PointSpread point_spread = instance.TallyScore();
QString user_name = QtUtils::FromStdString(instance.GetPlayerDisplayName());
QString game_name = QtUtils::FromStdString(instance.GetGameDisplayName());
AchievementManager::BadgeStatus player_badge = instance.GetPlayerBadge();
@ -83,9 +79,7 @@ void AchievementHeaderWidget::UpdateData()
m_user_icon->setVisible(false);
m_user_icon->clear();
m_user_icon->setText({});
if (Config::Get(Config::RA_BADGES_ENABLED))
{
if (!player_badge.name.empty())
if (Config::Get(Config::RA_BADGES_ENABLED) && !player_badge.name.empty())
{
QImage i_user_icon{};
if (i_user_icon.loadFromData(&player_badge.badge.front(), (int)player_badge.badge.size()))
@ -97,13 +91,16 @@ void AchievementHeaderWidget::UpdateData()
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 (instance.IsGameLoaded())
{
if (!game_badge.name.empty())
rc_client_user_game_summary_t game_summary;
rc_client_get_user_game_summary(instance.GetClient(), &game_summary);
if (Config::Get(Config::RA_BADGES_ENABLED) && !game_badge.name.empty())
{
QImage i_game_icon{};
if (i_game_icon.loadFromData(&game_badge.badge.front(), (int)game_badge.badge.size()))
@ -112,30 +109,29 @@ void AchievementHeaderWidget::UpdateData()
.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;
if (game_summary.num_core_achievements == game_summary.num_unlocked_achievements)
{
color =
instance.IsHardcoreModeActive() ? AchievementManager::GOLD : AchievementManager::BLUE;
}
m_game_icon->setStyleSheet(
QStringLiteral("border: 4px solid %1").arg(QString::fromStdString(std::string(color))));
QStringLiteral("border: 4px solid %1").arg(QtUtils::FromStdString(color)));
m_game_icon->setVisible(true);
}
}
}
if (!game_name.isEmpty())
{
m_name->setText(tr("%1 is playing %2").arg(user_name).arg(game_name));
m_points->setText(GetPointsString(user_name, point_spread));
m_points->setText(tr("%1 has unlocked %2/%3 achievements worth %4/%5 points")
.arg(user_name)
.arg(game_summary.num_unlocked_achievements)
.arg(game_summary.num_core_achievements)
.arg(game_summary.points_unlocked)
.arg(game_summary.points_core));
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_hard->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_game_progress->setRange(0, game_summary.num_core_achievements);
if (!m_game_progress->isVisible())
m_game_progress->setVisible(true);
m_game_progress->setValue(game_summary.num_unlocked_achievements);
m_rich_presence->setText(QString::fromUtf8(instance.GetRichPresence().data()));
if (!m_rich_presence->isVisible())
m_rich_presence->setVisible(Config::Get(Config::RA_RICH_PRESENCE_ENABLED));
@ -146,37 +142,10 @@ void AchievementHeaderWidget::UpdateData()
m_name->setText(user_name);
m_points->setText(tr("%1 points").arg(instance.GetPlayerScore()));
m_game_progress_hard->setVisible(false);
m_game_progress_soft->setVisible(false);
m_game_progress->setVisible(false);
m_rich_presence->setVisible(false);
m_locked_warning->setVisible(instance.IsDisabled());
}
}
QString
AchievementHeaderWidget::GetPointsString(const QString& user_name,
const AchievementManager::PointSpread& point_spread) const
{
if (point_spread.soft_points > 0)
{
return tr("%1 has unlocked %2/%3 achievements (%4 hardcore) worth %5/%6 points (%7 hardcore)")
.arg(user_name)
.arg(point_spread.hard_unlocks + point_spread.soft_unlocks)
.arg(point_spread.total_count)
.arg(point_spread.hard_unlocks)
.arg(point_spread.hard_points + point_spread.soft_points)
.arg(point_spread.total_points)
.arg(point_spread.hard_points);
}
else
{
return tr("%1 has unlocked %2/%3 achievements worth %4/%5 points")
.arg(user_name)
.arg(point_spread.hard_unlocks)
.arg(point_spread.total_count)
.arg(point_spread.hard_points)
.arg(point_spread.total_points);
}
}
#endif // USE_RETRO_ACHIEVEMENTS

View File

@ -20,15 +20,11 @@ public:
void UpdateData();
private:
QString GetPointsString(const QString& user_name,
const AchievementManager::PointSpread& point_spread) const;
QLabel* m_user_icon;
QLabel* m_game_icon;
QLabel* m_name;
QLabel* m_points;
QProgressBar* m_game_progress_hard;
QProgressBar* m_game_progress_soft;
QProgressBar* m_game_progress;
QLabel* m_rich_presence;
QLabel* m_locked_warning;
QGroupBox* m_header_box;