Merge pull request #11878 from LillyJadeKatrin/retroachievements-achievements-tab
RetroAchievements - Achievement Progress Tab
This commit is contained in:
commit
da2784a391
|
@ -38,11 +38,24 @@ void AchievementManager::Init()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void AchievementManager::SetUpdateCallback(UpdateCallback callback)
|
||||||
|
{
|
||||||
|
m_update_callback = std::move(callback);
|
||||||
|
m_update_callback();
|
||||||
|
}
|
||||||
|
|
||||||
AchievementManager::ResponseType AchievementManager::Login(const std::string& password)
|
AchievementManager::ResponseType AchievementManager::Login(const std::string& password)
|
||||||
{
|
{
|
||||||
if (!m_is_runtime_initialized)
|
if (!m_is_runtime_initialized)
|
||||||
return AchievementManager::ResponseType::MANAGER_NOT_INITIALIZED;
|
return AchievementManager::ResponseType::MANAGER_NOT_INITIALIZED;
|
||||||
return VerifyCredentials(password);
|
AchievementManager::ResponseType r_type = AchievementManager::ResponseType::UNKNOWN_FAILURE;
|
||||||
|
{
|
||||||
|
std::lock_guard lg{m_lock};
|
||||||
|
r_type = VerifyCredentials(password);
|
||||||
|
}
|
||||||
|
if (m_update_callback)
|
||||||
|
m_update_callback();
|
||||||
|
return r_type;
|
||||||
}
|
}
|
||||||
|
|
||||||
void AchievementManager::LoginAsync(const std::string& password, const ResponseCallback& callback)
|
void AchievementManager::LoginAsync(const std::string& password, const ResponseCallback& callback)
|
||||||
|
@ -52,7 +65,14 @@ void AchievementManager::LoginAsync(const std::string& password, const ResponseC
|
||||||
callback(AchievementManager::ResponseType::MANAGER_NOT_INITIALIZED);
|
callback(AchievementManager::ResponseType::MANAGER_NOT_INITIALIZED);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
m_queue.EmplaceItem([this, password, callback] { callback(VerifyCredentials(password)); });
|
m_queue.EmplaceItem([this, password, callback] {
|
||||||
|
{
|
||||||
|
std::lock_guard lg{m_lock};
|
||||||
|
callback(VerifyCredentials(password));
|
||||||
|
}
|
||||||
|
if (m_update_callback)
|
||||||
|
m_update_callback();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
bool AchievementManager::IsLoggedIn() const
|
bool AchievementManager::IsLoggedIn() const
|
||||||
|
@ -141,11 +161,11 @@ void AchievementManager::LoadGameByFilenameAsync(const std::string& iso_path,
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto fetch_game_data_response = FetchGameData();
|
const auto fetch_game_data_response = FetchGameData();
|
||||||
m_is_game_loaded = fetch_game_data_response == ResponseType::SUCCESS;
|
if (fetch_game_data_response != ResponseType::SUCCESS)
|
||||||
if (!m_is_game_loaded)
|
|
||||||
{
|
{
|
||||||
OSD::AddMessage("Unable to retrieve data from RetroAchievements server.",
|
OSD::AddMessage("Unable to retrieve data from RetroAchievements server.",
|
||||||
OSD::Duration::VERY_LONG, OSD::Color::RED);
|
OSD::Duration::VERY_LONG, OSD::Color::RED);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Claim the lock, then queue the fetch unlock data calls, then initialize the unlock map in
|
// Claim the lock, then queue the fetch unlock data calls, then initialize the unlock map in
|
||||||
|
@ -154,6 +174,7 @@ void AchievementManager::LoadGameByFilenameAsync(const std::string& iso_path,
|
||||||
// it.
|
// it.
|
||||||
{
|
{
|
||||||
std::lock_guard lg{m_lock};
|
std::lock_guard lg{m_lock};
|
||||||
|
m_is_game_loaded = true;
|
||||||
LoadUnlockData([](ResponseType r_type) {});
|
LoadUnlockData([](ResponseType r_type) {});
|
||||||
ActivateDeactivateAchievements();
|
ActivateDeactivateAchievements();
|
||||||
PointSpread spread = TallyScore();
|
PointSpread spread = TallyScore();
|
||||||
|
@ -179,10 +200,17 @@ void AchievementManager::LoadGameByFilenameAsync(const std::string& iso_path,
|
||||||
// Reset this to zero so that RP immediately triggers on the first frame
|
// Reset this to zero so that RP immediately triggers on the first frame
|
||||||
m_last_ping_time = 0;
|
m_last_ping_time = 0;
|
||||||
|
|
||||||
|
if (m_update_callback)
|
||||||
|
m_update_callback();
|
||||||
callback(fetch_game_data_response);
|
callback(fetch_game_data_response);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool AchievementManager::IsGameLoaded() const
|
||||||
|
{
|
||||||
|
return m_is_game_loaded;
|
||||||
|
}
|
||||||
|
|
||||||
void AchievementManager::LoadUnlockData(const ResponseCallback& callback)
|
void AchievementManager::LoadUnlockData(const ResponseCallback& callback)
|
||||||
{
|
{
|
||||||
m_queue.EmplaceItem([this, callback] {
|
m_queue.EmplaceItem([this, callback] {
|
||||||
|
@ -194,6 +222,8 @@ void AchievementManager::LoadUnlockData(const ResponseCallback& callback)
|
||||||
}
|
}
|
||||||
|
|
||||||
callback(FetchUnlockData(false));
|
callback(FetchUnlockData(false));
|
||||||
|
if (m_update_callback)
|
||||||
|
m_update_callback();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -296,39 +326,114 @@ u32 AchievementManager::MemoryPeeker(u32 address, u32 num_bytes, void* ud)
|
||||||
|
|
||||||
void AchievementManager::AchievementEventHandler(const rc_runtime_event_t* runtime_event)
|
void AchievementManager::AchievementEventHandler(const rc_runtime_event_t* runtime_event)
|
||||||
{
|
{
|
||||||
switch (runtime_event->type)
|
|
||||||
{
|
{
|
||||||
case RC_RUNTIME_EVENT_ACHIEVEMENT_TRIGGERED:
|
std::lock_guard lg{m_lock};
|
||||||
HandleAchievementTriggeredEvent(runtime_event);
|
switch (runtime_event->type)
|
||||||
break;
|
{
|
||||||
case RC_RUNTIME_EVENT_LBOARD_STARTED:
|
case RC_RUNTIME_EVENT_ACHIEVEMENT_TRIGGERED:
|
||||||
HandleLeaderboardStartedEvent(runtime_event);
|
HandleAchievementTriggeredEvent(runtime_event);
|
||||||
break;
|
break;
|
||||||
case RC_RUNTIME_EVENT_LBOARD_CANCELED:
|
case RC_RUNTIME_EVENT_LBOARD_STARTED:
|
||||||
HandleLeaderboardCanceledEvent(runtime_event);
|
HandleLeaderboardStartedEvent(runtime_event);
|
||||||
break;
|
break;
|
||||||
case RC_RUNTIME_EVENT_LBOARD_TRIGGERED:
|
case RC_RUNTIME_EVENT_LBOARD_CANCELED:
|
||||||
HandleLeaderboardTriggeredEvent(runtime_event);
|
HandleLeaderboardCanceledEvent(runtime_event);
|
||||||
break;
|
break;
|
||||||
|
case RC_RUNTIME_EVENT_LBOARD_TRIGGERED:
|
||||||
|
HandleLeaderboardTriggeredEvent(runtime_event);
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
if (m_update_callback)
|
||||||
|
m_update_callback();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::recursive_mutex* AchievementManager::GetLock()
|
||||||
|
{
|
||||||
|
return &m_lock;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string AchievementManager::GetPlayerDisplayName() const
|
||||||
|
{
|
||||||
|
return IsLoggedIn() ? m_display_name : "";
|
||||||
|
}
|
||||||
|
|
||||||
|
u32 AchievementManager::GetPlayerScore() const
|
||||||
|
{
|
||||||
|
return IsLoggedIn() ? m_player_score : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string AchievementManager::GetGameDisplayName() const
|
||||||
|
{
|
||||||
|
return IsGameLoaded() ? m_game_data.title : "";
|
||||||
|
}
|
||||||
|
|
||||||
|
AchievementManager::PointSpread AchievementManager::TallyScore() const
|
||||||
|
{
|
||||||
|
PointSpread spread{};
|
||||||
|
if (!IsGameLoaded())
|
||||||
|
return spread;
|
||||||
|
for (const auto& entry : m_unlock_map)
|
||||||
|
{
|
||||||
|
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_api_fetch_game_data_response_t* AchievementManager::GetGameData()
|
||||||
|
{
|
||||||
|
return &m_game_data;
|
||||||
|
}
|
||||||
|
|
||||||
|
AchievementManager::UnlockStatus
|
||||||
|
AchievementManager::GetUnlockStatus(AchievementId achievement_id) const
|
||||||
|
{
|
||||||
|
return m_unlock_map.at(achievement_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AchievementManager::GetAchievementProgress(AchievementId achievement_id, u32* value,
|
||||||
|
u32* target)
|
||||||
|
{
|
||||||
|
rc_runtime_get_achievement_measured(&m_runtime, achievement_id, value, target);
|
||||||
}
|
}
|
||||||
|
|
||||||
void AchievementManager::CloseGame()
|
void AchievementManager::CloseGame()
|
||||||
{
|
{
|
||||||
m_is_game_loaded = false;
|
{
|
||||||
m_game_id = 0;
|
std::lock_guard lg{m_lock};
|
||||||
m_queue.Cancel();
|
m_is_game_loaded = false;
|
||||||
m_unlock_map.clear();
|
m_game_id = 0;
|
||||||
m_system = nullptr;
|
m_queue.Cancel();
|
||||||
ActivateDeactivateAchievements();
|
m_unlock_map.clear();
|
||||||
ActivateDeactivateLeaderboards();
|
m_system = nullptr;
|
||||||
ActivateDeactivateRichPresence();
|
ActivateDeactivateAchievements();
|
||||||
|
ActivateDeactivateLeaderboards();
|
||||||
|
ActivateDeactivateRichPresence();
|
||||||
|
}
|
||||||
|
if (m_update_callback)
|
||||||
|
m_update_callback();
|
||||||
}
|
}
|
||||||
|
|
||||||
void AchievementManager::Logout()
|
void AchievementManager::Logout()
|
||||||
{
|
{
|
||||||
CloseGame();
|
CloseGame();
|
||||||
Config::SetBaseOrCurrent(Config::RA_API_TOKEN, "");
|
Config::SetBaseOrCurrent(Config::RA_API_TOKEN, "");
|
||||||
|
if (m_update_callback)
|
||||||
|
m_update_callback();
|
||||||
}
|
}
|
||||||
|
|
||||||
void AchievementManager::Shutdown()
|
void AchievementManager::Shutdown()
|
||||||
|
@ -353,6 +458,7 @@ AchievementManager::ResponseType AchievementManager::VerifyCredentials(const std
|
||||||
{
|
{
|
||||||
Config::SetBaseOrCurrent(Config::RA_API_TOKEN, login_data.api_token);
|
Config::SetBaseOrCurrent(Config::RA_API_TOKEN, login_data.api_token);
|
||||||
m_display_name = login_data.display_name;
|
m_display_name = login_data.display_name;
|
||||||
|
m_player_score = login_data.score;
|
||||||
}
|
}
|
||||||
rc_api_destroy_login_response(&login_data);
|
rc_api_destroy_login_response(&login_data);
|
||||||
return r_type;
|
return r_type;
|
||||||
|
@ -618,30 +724,6 @@ void AchievementManager::HandleLeaderboardTriggeredEvent(const rc_runtime_event_
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
AchievementManager::PointSpread AchievementManager::TallyScore() const
|
|
||||||
{
|
|
||||||
PointSpread spread{};
|
|
||||||
for (const auto& entry : m_unlock_map)
|
|
||||||
{
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Every RetroAchievements API call, with only a partial exception for fetch_image, follows
|
// 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):
|
// 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
|
// Create a specific rc_api_X_request_t struct and populate with the necessary values
|
||||||
|
|
|
@ -41,6 +41,7 @@ public:
|
||||||
UNKNOWN_FAILURE
|
UNKNOWN_FAILURE
|
||||||
};
|
};
|
||||||
using ResponseCallback = std::function<void(ResponseType)>;
|
using ResponseCallback = std::function<void(ResponseType)>;
|
||||||
|
using UpdateCallback = std::function<void()>;
|
||||||
|
|
||||||
struct PointSpread
|
struct PointSpread
|
||||||
{
|
{
|
||||||
|
@ -52,12 +53,27 @@ public:
|
||||||
u32 soft_points;
|
u32 soft_points;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
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;
|
||||||
|
};
|
||||||
|
|
||||||
static AchievementManager* GetInstance();
|
static AchievementManager* GetInstance();
|
||||||
void Init();
|
void Init();
|
||||||
|
void SetUpdateCallback(UpdateCallback callback);
|
||||||
ResponseType Login(const std::string& password);
|
ResponseType Login(const std::string& password);
|
||||||
void LoginAsync(const std::string& password, const ResponseCallback& callback);
|
void LoginAsync(const std::string& password, const ResponseCallback& callback);
|
||||||
bool IsLoggedIn() const;
|
bool IsLoggedIn() const;
|
||||||
void LoadGameByFilenameAsync(const std::string& iso_path, const ResponseCallback& callback);
|
void LoadGameByFilenameAsync(const std::string& iso_path, const ResponseCallback& callback);
|
||||||
|
bool IsGameLoaded() const;
|
||||||
|
|
||||||
void LoadUnlockData(const ResponseCallback& callback);
|
void LoadUnlockData(const ResponseCallback& callback);
|
||||||
void ActivateDeactivateAchievements();
|
void ActivateDeactivateAchievements();
|
||||||
|
@ -68,6 +84,15 @@ public:
|
||||||
u32 MemoryPeeker(u32 address, u32 num_bytes, void* ud);
|
u32 MemoryPeeker(u32 address, u32 num_bytes, void* ud);
|
||||||
void AchievementEventHandler(const rc_runtime_event_t* runtime_event);
|
void AchievementEventHandler(const rc_runtime_event_t* runtime_event);
|
||||||
|
|
||||||
|
std::recursive_mutex* GetLock();
|
||||||
|
std::string GetPlayerDisplayName() const;
|
||||||
|
u32 GetPlayerScore() const;
|
||||||
|
std::string GetGameDisplayName() const;
|
||||||
|
PointSpread TallyScore() const;
|
||||||
|
rc_api_fetch_game_data_response_t* GetGameData();
|
||||||
|
UnlockStatus GetUnlockStatus(AchievementId achievement_id) const;
|
||||||
|
void GetAchievementProgress(AchievementId achievement_id, u32* value, u32* target);
|
||||||
|
|
||||||
void CloseGame();
|
void CloseGame();
|
||||||
void Logout();
|
void Logout();
|
||||||
void Shutdown();
|
void Shutdown();
|
||||||
|
@ -95,8 +120,6 @@ private:
|
||||||
void HandleLeaderboardCanceledEvent(const rc_runtime_event_t* runtime_event);
|
void HandleLeaderboardCanceledEvent(const rc_runtime_event_t* runtime_event);
|
||||||
void HandleLeaderboardTriggeredEvent(const rc_runtime_event_t* runtime_event);
|
void HandleLeaderboardTriggeredEvent(const rc_runtime_event_t* runtime_event);
|
||||||
|
|
||||||
PointSpread TallyScore() const;
|
|
||||||
|
|
||||||
template <typename RcRequest, typename RcResponse>
|
template <typename RcRequest, typename RcResponse>
|
||||||
ResponseType Request(RcRequest rc_request, RcResponse* rc_response,
|
ResponseType Request(RcRequest rc_request, RcResponse* rc_response,
|
||||||
const std::function<int(rc_api_request_t*, const RcRequest*)>& init_request,
|
const std::function<int(rc_api_request_t*, const RcRequest*)>& init_request,
|
||||||
|
@ -105,25 +128,15 @@ private:
|
||||||
rc_runtime_t m_runtime{};
|
rc_runtime_t m_runtime{};
|
||||||
Core::System* m_system{};
|
Core::System* m_system{};
|
||||||
bool m_is_runtime_initialized = false;
|
bool m_is_runtime_initialized = false;
|
||||||
|
UpdateCallback m_update_callback;
|
||||||
std::string m_display_name;
|
std::string m_display_name;
|
||||||
|
u32 m_player_score = 0;
|
||||||
std::array<char, HASH_LENGTH> m_game_hash{};
|
std::array<char, HASH_LENGTH> m_game_hash{};
|
||||||
u32 m_game_id = 0;
|
u32 m_game_id = 0;
|
||||||
rc_api_fetch_game_data_response_t m_game_data{};
|
rc_api_fetch_game_data_response_t m_game_data{};
|
||||||
bool m_is_game_loaded = false;
|
bool m_is_game_loaded = false;
|
||||||
time_t m_last_ping_time = 0;
|
time_t m_last_ping_time = 0;
|
||||||
|
|
||||||
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;
|
|
||||||
};
|
|
||||||
std::unordered_map<AchievementId, UnlockStatus> m_unlock_map;
|
std::unordered_map<AchievementId, UnlockStatus> m_unlock_map;
|
||||||
|
|
||||||
Common::WorkQueueThread<std::function<void()>> m_queue;
|
Common::WorkQueueThread<std::function<void()>> m_queue;
|
||||||
|
|
|
@ -0,0 +1,140 @@
|
||||||
|
// Copyright 2023 Dolphin Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#ifdef USE_RETRO_ACHIEVEMENTS
|
||||||
|
#include "DolphinQt/Achievements/AchievementHeaderWidget.h"
|
||||||
|
|
||||||
|
#include <QCheckBox>
|
||||||
|
#include <QGroupBox>
|
||||||
|
#include <QLabel>
|
||||||
|
#include <QLineEdit>
|
||||||
|
#include <QProgressBar>
|
||||||
|
#include <QPushButton>
|
||||||
|
#include <QString>
|
||||||
|
#include <QVBoxLayout>
|
||||||
|
|
||||||
|
#include <fmt/format.h>
|
||||||
|
|
||||||
|
#include <rcheevos/include/rc_api_runtime.h>
|
||||||
|
#include <rcheevos/include/rc_api_user.h>
|
||||||
|
#include <rcheevos/include/rc_runtime.h>
|
||||||
|
|
||||||
|
#include "Core/AchievementManager.h"
|
||||||
|
#include "Core/Core.h"
|
||||||
|
|
||||||
|
#include "DolphinQt/QtUtils/ModalMessageBox.h"
|
||||||
|
#include "DolphinQt/QtUtils/NonDefaultQPushButton.h"
|
||||||
|
#include "DolphinQt/QtUtils/SignalBlocking.h"
|
||||||
|
#include "DolphinQt/Settings.h"
|
||||||
|
|
||||||
|
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_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);
|
||||||
|
|
||||||
|
QVBoxLayout* m_total = new QVBoxLayout();
|
||||||
|
m_total->addWidget(m_user_box);
|
||||||
|
m_total->addWidget(m_game_box);
|
||||||
|
|
||||||
|
UpdateData();
|
||||||
|
|
||||||
|
m_total->setContentsMargins(0, 0, 0, 0);
|
||||||
|
m_total->setAlignment(Qt::AlignTop);
|
||||||
|
setLayout(m_total);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AchievementHeaderWidget::UpdateData()
|
||||||
|
{
|
||||||
|
if (!AchievementManager::GetInstance()->IsLoggedIn())
|
||||||
|
{
|
||||||
|
m_user_box->setVisible(false);
|
||||||
|
m_game_box->setVisible(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
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()));
|
||||||
|
|
||||||
|
if (!AchievementManager::GetInstance()->IsGameLoaded())
|
||||||
|
{
|
||||||
|
m_user_box->setVisible(true);
|
||||||
|
m_game_box->setVisible(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
// TODO: RP needs a minor refactor to work here, will be a future PR
|
||||||
|
// m_rich_presence->setText(QString::fromStdString(AchievementManager::GetInstance()->GenerateRichPresence()));
|
||||||
|
// m_rich_presence->setVisible(Config::Get(Config::RA_RICH_PRESENCE_ENABLED));
|
||||||
|
m_rich_presence->setText(QString{});
|
||||||
|
m_rich_presence->setVisible(false);
|
||||||
|
|
||||||
|
m_user_box->setVisible(false);
|
||||||
|
m_game_box->setVisible(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
|
@ -0,0 +1,42 @@
|
||||||
|
// Copyright 2023 Dolphin Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#ifdef USE_RETRO_ACHIEVEMENTS
|
||||||
|
#include <QWidget>
|
||||||
|
|
||||||
|
#include "Core/AchievementManager.h"
|
||||||
|
|
||||||
|
class QGroupBox;
|
||||||
|
class QLabel;
|
||||||
|
class QProgressBar;
|
||||||
|
class QVBoxLayout;
|
||||||
|
|
||||||
|
class AchievementHeaderWidget final : public QWidget
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
explicit AchievementHeaderWidget(QWidget* parent);
|
||||||
|
void UpdateData();
|
||||||
|
|
||||||
|
private:
|
||||||
|
QString GetPointsString(const QString& user_name,
|
||||||
|
const AchievementManager::PointSpread& point_spread) const;
|
||||||
|
|
||||||
|
QGroupBox* m_common_box;
|
||||||
|
QVBoxLayout* m_common_layout;
|
||||||
|
|
||||||
|
QLabel* m_user_name;
|
||||||
|
QLabel* m_user_points;
|
||||||
|
QLabel* m_game_name;
|
||||||
|
QLabel* m_game_points;
|
||||||
|
QProgressBar* m_game_progress_hard;
|
||||||
|
QProgressBar* m_game_progress_soft;
|
||||||
|
QLabel* m_rich_presence;
|
||||||
|
|
||||||
|
QGroupBox* m_user_box;
|
||||||
|
QGroupBox* m_game_box;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // USE_RETRO_ACHIEVEMENTS
|
|
@ -0,0 +1,124 @@
|
||||||
|
// Copyright 2023 Dolphin Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#ifdef USE_RETRO_ACHIEVEMENTS
|
||||||
|
#include "DolphinQt/Achievements/AchievementProgressWidget.h"
|
||||||
|
|
||||||
|
#include <QCheckBox>
|
||||||
|
#include <QGroupBox>
|
||||||
|
#include <QLabel>
|
||||||
|
#include <QLineEdit>
|
||||||
|
#include <QProgressBar>
|
||||||
|
#include <QPushButton>
|
||||||
|
#include <QString>
|
||||||
|
#include <QVBoxLayout>
|
||||||
|
|
||||||
|
#include <fmt/format.h>
|
||||||
|
|
||||||
|
#include <rcheevos/include/rc_api_runtime.h>
|
||||||
|
#include <rcheevos/include/rc_api_user.h>
|
||||||
|
#include <rcheevos/include/rc_runtime.h>
|
||||||
|
|
||||||
|
#include "Core/AchievementManager.h"
|
||||||
|
#include "Core/Config/AchievementSettings.h"
|
||||||
|
#include "Core/Config/MainSettings.h"
|
||||||
|
#include "Core/Core.h"
|
||||||
|
|
||||||
|
#include "DolphinQt/Config/ControllerInterface/ControllerInterfaceWindow.h"
|
||||||
|
#include "DolphinQt/QtUtils/ModalMessageBox.h"
|
||||||
|
#include "DolphinQt/QtUtils/NonDefaultQPushButton.h"
|
||||||
|
#include "DolphinQt/QtUtils/SignalBlocking.h"
|
||||||
|
#include "DolphinQt/Settings.h"
|
||||||
|
|
||||||
|
AchievementProgressWidget::AchievementProgressWidget(QWidget* parent) : QWidget(parent)
|
||||||
|
{
|
||||||
|
m_common_box = new QGroupBox();
|
||||||
|
m_common_layout = new QVBoxLayout();
|
||||||
|
|
||||||
|
UpdateData();
|
||||||
|
|
||||||
|
m_common_box->setLayout(m_common_layout);
|
||||||
|
|
||||||
|
auto* layout = new QVBoxLayout;
|
||||||
|
layout->setContentsMargins(0, 0, 0, 0);
|
||||||
|
layout->setAlignment(Qt::AlignTop);
|
||||||
|
layout->addWidget(m_common_box);
|
||||||
|
setLayout(layout);
|
||||||
|
}
|
||||||
|
|
||||||
|
QGroupBox*
|
||||||
|
AchievementProgressWidget::CreateAchievementBox(const rc_api_achievement_definition_t* achievement)
|
||||||
|
{
|
||||||
|
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();
|
||||||
|
unsigned int value = 0;
|
||||||
|
unsigned int target = 0;
|
||||||
|
AchievementManager::GetInstance()->GetAchievementProgress(achievement->id, &value, &target);
|
||||||
|
if (target > 0)
|
||||||
|
{
|
||||||
|
a_progress_bar->setRange(0, target);
|
||||||
|
a_progress_bar->setValue(value);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
a_progress_bar->setVisible(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
QVBoxLayout* a_col_right = new QVBoxLayout();
|
||||||
|
a_col_right->addWidget(a_title);
|
||||||
|
a_col_right->addWidget(a_description);
|
||||||
|
a_col_right->addWidget(a_points);
|
||||||
|
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->addLayout(a_col_right);
|
||||||
|
QGroupBox* a_group_box = new QGroupBox();
|
||||||
|
a_group_box->setLayout(a_total);
|
||||||
|
return a_group_box;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AchievementProgressWidget::UpdateData()
|
||||||
|
{
|
||||||
|
QLayoutItem* item;
|
||||||
|
while ((item = m_common_layout->layout()->takeAt(0)) != nullptr)
|
||||||
|
{
|
||||||
|
delete item->widget();
|
||||||
|
delete item;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto* game_data = AchievementManager::GetInstance()->GetGameData();
|
||||||
|
for (u32 ix = 0; ix < game_data->num_achievements; ix++)
|
||||||
|
{
|
||||||
|
m_common_layout->addWidget(CreateAchievementBox(game_data->achievements + ix));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QString AchievementProgressWidget::GetStatusString(u32 achievement_id) const
|
||||||
|
{
|
||||||
|
const auto unlock_status = AchievementManager::GetInstance()->GetUnlockStatus(achievement_id);
|
||||||
|
if (unlock_status.session_unlock_count > 0)
|
||||||
|
{
|
||||||
|
if (Config::Get(Config::RA_ENCORE_ENABLED))
|
||||||
|
{
|
||||||
|
return tr("Unlocked %1 times this session").arg(unlock_status.session_unlock_count);
|
||||||
|
}
|
||||||
|
return tr("Unlocked this session");
|
||||||
|
}
|
||||||
|
switch (unlock_status.remote_unlock_status)
|
||||||
|
{
|
||||||
|
case AchievementManager::UnlockStatus::UnlockType::LOCKED:
|
||||||
|
return tr("Locked");
|
||||||
|
case AchievementManager::UnlockStatus::UnlockType::SOFTCORE:
|
||||||
|
return tr("Unlocked (Casual)");
|
||||||
|
case AchievementManager::UnlockStatus::UnlockType::HARDCORE:
|
||||||
|
return tr("Unlocked");
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // USE_RETRO_ACHIEVEMENTS
|
|
@ -0,0 +1,34 @@
|
||||||
|
// Copyright 2023 Dolphin Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#ifdef USE_RETRO_ACHIEVEMENTS
|
||||||
|
#include <QWidget>
|
||||||
|
|
||||||
|
#include "Common/CommonTypes.h"
|
||||||
|
|
||||||
|
class QCheckBox;
|
||||||
|
class QGroupBox;
|
||||||
|
class QLineEdit;
|
||||||
|
class QPushButton;
|
||||||
|
class QVBoxLayout;
|
||||||
|
|
||||||
|
struct rc_api_achievement_definition_t;
|
||||||
|
|
||||||
|
class AchievementProgressWidget final : public QWidget
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
explicit AchievementProgressWidget(QWidget* parent);
|
||||||
|
void UpdateData();
|
||||||
|
|
||||||
|
private:
|
||||||
|
QGroupBox* CreateAchievementBox(const rc_api_achievement_definition_t* achievement);
|
||||||
|
QString GetStatusString(u32 achievement_id) const;
|
||||||
|
|
||||||
|
QGroupBox* m_common_box;
|
||||||
|
QVBoxLayout* m_common_layout;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // USE_RETRO_ACHIEVEMENTS
|
|
@ -4,11 +4,16 @@
|
||||||
#ifdef USE_RETRO_ACHIEVEMENTS
|
#ifdef USE_RETRO_ACHIEVEMENTS
|
||||||
#include "DolphinQt/Achievements/AchievementsWindow.h"
|
#include "DolphinQt/Achievements/AchievementsWindow.h"
|
||||||
|
|
||||||
|
#include <mutex>
|
||||||
|
|
||||||
#include <QDialogButtonBox>
|
#include <QDialogButtonBox>
|
||||||
#include <QTabWidget>
|
#include <QTabWidget>
|
||||||
#include <QVBoxLayout>
|
#include <QVBoxLayout>
|
||||||
|
|
||||||
|
#include "DolphinQt/Achievements/AchievementHeaderWidget.h"
|
||||||
|
#include "DolphinQt/Achievements/AchievementProgressWidget.h"
|
||||||
#include "DolphinQt/Achievements/AchievementSettingsWidget.h"
|
#include "DolphinQt/Achievements/AchievementSettingsWidget.h"
|
||||||
|
#include "DolphinQt/QtUtils/QueueOnObject.h"
|
||||||
#include "DolphinQt/QtUtils/WrapInScrollArea.h"
|
#include "DolphinQt/QtUtils/WrapInScrollArea.h"
|
||||||
|
|
||||||
AchievementsWindow::AchievementsWindow(QWidget* parent) : QDialog(parent)
|
AchievementsWindow::AchievementsWindow(QWidget* parent) : QDialog(parent)
|
||||||
|
@ -18,6 +23,8 @@ AchievementsWindow::AchievementsWindow(QWidget* parent) : QDialog(parent)
|
||||||
|
|
||||||
CreateMainLayout();
|
CreateMainLayout();
|
||||||
ConnectWidgets();
|
ConnectWidgets();
|
||||||
|
AchievementManager::GetInstance()->SetUpdateCallback(
|
||||||
|
[this] { QueueOnObject(this, &AchievementsWindow::UpdateData); });
|
||||||
}
|
}
|
||||||
|
|
||||||
void AchievementsWindow::showEvent(QShowEvent* event)
|
void AchievementsWindow::showEvent(QShowEvent* event)
|
||||||
|
@ -30,13 +37,18 @@ void AchievementsWindow::CreateMainLayout()
|
||||||
{
|
{
|
||||||
auto* layout = new QVBoxLayout();
|
auto* layout = new QVBoxLayout();
|
||||||
|
|
||||||
|
m_header_widget = new AchievementHeaderWidget(this);
|
||||||
m_tab_widget = new QTabWidget();
|
m_tab_widget = new QTabWidget();
|
||||||
|
m_progress_widget = new AchievementProgressWidget(m_tab_widget);
|
||||||
m_tab_widget->addTab(
|
m_tab_widget->addTab(
|
||||||
GetWrappedWidget(new AchievementSettingsWidget(m_tab_widget, this), this, 125, 100),
|
GetWrappedWidget(new AchievementSettingsWidget(m_tab_widget, this), this, 125, 100),
|
||||||
tr("Settings"));
|
tr("Settings"));
|
||||||
|
m_tab_widget->addTab(GetWrappedWidget(m_progress_widget, this, 125, 100), tr("Progress"));
|
||||||
|
m_tab_widget->setTabVisible(1, AchievementManager::GetInstance()->IsGameLoaded());
|
||||||
|
|
||||||
m_button_box = new QDialogButtonBox(QDialogButtonBox::Close);
|
m_button_box = new QDialogButtonBox(QDialogButtonBox::Close);
|
||||||
|
|
||||||
|
layout->addWidget(m_header_widget);
|
||||||
layout->addWidget(m_tab_widget);
|
layout->addWidget(m_tab_widget);
|
||||||
layout->addWidget(m_button_box);
|
layout->addWidget(m_button_box);
|
||||||
|
|
||||||
|
@ -50,6 +62,14 @@ void AchievementsWindow::ConnectWidgets()
|
||||||
|
|
||||||
void AchievementsWindow::UpdateData()
|
void AchievementsWindow::UpdateData()
|
||||||
{
|
{
|
||||||
|
{
|
||||||
|
std::lock_guard lg{*AchievementManager::GetInstance()->GetLock()};
|
||||||
|
m_header_widget->UpdateData();
|
||||||
|
m_header_widget->setVisible(AchievementManager::GetInstance()->IsLoggedIn());
|
||||||
|
// Settings tab handles its own updates ... indeed, that calls this
|
||||||
|
m_progress_widget->UpdateData();
|
||||||
|
m_tab_widget->setTabVisible(1, AchievementManager::GetInstance()->IsGameLoaded());
|
||||||
|
}
|
||||||
update();
|
update();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,8 +6,14 @@
|
||||||
#ifdef USE_RETRO_ACHIEVEMENTS
|
#ifdef USE_RETRO_ACHIEVEMENTS
|
||||||
#include <QDialog>
|
#include <QDialog>
|
||||||
|
|
||||||
class QTabWidget;
|
#include "Core/AchievementManager.h"
|
||||||
|
#include "DolphinQt/QtUtils/QueueOnObject.h"
|
||||||
|
|
||||||
|
class AchievementHeaderWidget;
|
||||||
|
class AchievementProgressWidget;
|
||||||
class QDialogButtonBox;
|
class QDialogButtonBox;
|
||||||
|
class QTabWidget;
|
||||||
|
class UpdateCallback;
|
||||||
|
|
||||||
class AchievementsWindow : public QDialog
|
class AchievementsWindow : public QDialog
|
||||||
{
|
{
|
||||||
|
@ -21,7 +27,9 @@ private:
|
||||||
void showEvent(QShowEvent* event);
|
void showEvent(QShowEvent* event);
|
||||||
void ConnectWidgets();
|
void ConnectWidgets();
|
||||||
|
|
||||||
|
AchievementHeaderWidget* m_header_widget;
|
||||||
QTabWidget* m_tab_widget;
|
QTabWidget* m_tab_widget;
|
||||||
|
AchievementProgressWidget* m_progress_widget;
|
||||||
QDialogButtonBox* m_button_box;
|
QDialogButtonBox* m_button_box;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -27,6 +27,10 @@ add_executable(dolphin-emu
|
||||||
CheatSearchWidget.h
|
CheatSearchWidget.h
|
||||||
CheatsManager.cpp
|
CheatsManager.cpp
|
||||||
CheatsManager.h
|
CheatsManager.h
|
||||||
|
Achievements/AchievementHeaderWidget.cpp
|
||||||
|
Achievements/AchievementHeaderWidget.h
|
||||||
|
Achievements/AchievementProgressWidget.cpp
|
||||||
|
Achievements/AchievementProgressWidget.h
|
||||||
Achievements/AchievementSettingsWidget.cpp
|
Achievements/AchievementSettingsWidget.cpp
|
||||||
Achievements/AchievementSettingsWidget.h
|
Achievements/AchievementSettingsWidget.h
|
||||||
Achievements/AchievementsWindow.cpp
|
Achievements/AchievementsWindow.cpp
|
||||||
|
|
|
@ -50,6 +50,8 @@
|
||||||
<ClCompile Include="CheatSearchFactoryWidget.cpp" />
|
<ClCompile Include="CheatSearchFactoryWidget.cpp" />
|
||||||
<ClCompile Include="CheatSearchWidget.cpp" />
|
<ClCompile Include="CheatSearchWidget.cpp" />
|
||||||
<ClCompile Include="CheatsManager.cpp" />
|
<ClCompile Include="CheatsManager.cpp" />
|
||||||
|
<ClCompile Include="Achievements\AchievementHeaderWidget.cpp" />
|
||||||
|
<ClCompile Include="Achievements\AchievementProgressWidget.cpp" />
|
||||||
<ClCompile Include="Achievements\AchievementSettingsWidget.cpp" />
|
<ClCompile Include="Achievements\AchievementSettingsWidget.cpp" />
|
||||||
<ClCompile Include="Achievements\AchievementsWindow.cpp" />
|
<ClCompile Include="Achievements\AchievementsWindow.cpp" />
|
||||||
<ClCompile Include="Config\ARCodeWidget.cpp" />
|
<ClCompile Include="Config\ARCodeWidget.cpp" />
|
||||||
|
@ -256,6 +258,8 @@
|
||||||
<QtMoc Include="CheatSearchFactoryWidget.h" />
|
<QtMoc Include="CheatSearchFactoryWidget.h" />
|
||||||
<QtMoc Include="CheatSearchWidget.h" />
|
<QtMoc Include="CheatSearchWidget.h" />
|
||||||
<QtMoc Include="CheatsManager.h" />
|
<QtMoc Include="CheatsManager.h" />
|
||||||
|
<QtMoc Include="Achievements\AchievementHeaderWidget.h" />
|
||||||
|
<QtMoc Include="Achievements\AchievementProgressWidget.h" />
|
||||||
<QtMoc Include="Achievements\AchievementSettingsWidget.h" />
|
<QtMoc Include="Achievements\AchievementSettingsWidget.h" />
|
||||||
<QtMoc Include="Achievements\AchievementsWindow.h" />
|
<QtMoc Include="Achievements\AchievementsWindow.h" />
|
||||||
<QtMoc Include="Config\ARCodeWidget.h" />
|
<QtMoc Include="Config\ARCodeWidget.h" />
|
||||||
|
|
Loading…
Reference in New Issue