Merge pull request #17629 from hrydgard/retroachievements-leaderboards

RetroAchievements: Add function to view leaderboards
This commit is contained in:
Henrik Rydgård 2023-06-27 13:38:22 +02:00 committed by GitHub
commit 5e3faafe01
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 214 additions and 20 deletions

View File

@ -1430,7 +1430,7 @@ static Path GetSecretPath(const char *nameOfSecret) {
// name should be simple alphanumerics to avoid problems on Windows.
void NativeSaveSecret(const char *nameOfSecret, const std::string &data) {
Path path = GetSecretPath(nameOfSecret);
if (!File::WriteDataToFile(false, data.data(), data.size(), path)) {
if (!File::WriteDataToFile(false, data.data(), (unsigned int)data.size(), path)) {
WARN_LOG(SYSTEM, "Failed to write secret '%s' to path '%s'", nameOfSecret, path.c_str());
}
}

View File

@ -10,15 +10,29 @@
#include "Core/Config.h"
void RetroAchievementsListScreen::CreateTabs() {
auto ac = GetI18NCategory(I18NCat::ACHIEVEMENTS);
UI::LinearLayout *achievements = AddTab("Achievements", ac->T("Achievements"));
achievements->SetSpacing(5.0f);
CreateAchievementsTab(achievements);
if (Achievements::GetLeaderboardCount() > 0) {
UI::LinearLayout *leaderboards = AddTab("Leaderboards", ac->T("Leaderboards"));
leaderboards->SetSpacing(5.0f);
CreateLeaderboardsTab(leaderboards);
}
#ifdef _DEBUG
CreateStatisticsTab(AddTab("AchievementsStatistics", ac->T("Statistics")));
#endif
}
void RetroAchievementsListScreen::CreateAchievementsTab(UI::ViewGroup *achievements) {
auto di = GetI18NCategory(I18NCat::DIALOG);
auto ac = GetI18NCategory(I18NCat::ACHIEVEMENTS);
using namespace UI;
LinearLayout *achievements = AddTab("Achievements", ac->T("Achievements"));
achievements->SetSpacing(5.0f);
std::vector<Achievements::Achievement> unlockedAchievements;
std::vector<Achievements::Achievement> lockedAchievements;
@ -33,29 +47,102 @@ void RetroAchievementsListScreen::CreateTabs() {
unlockedAchievements.push_back(achievement);
}
return true;
});
});
achievements->Add(new ItemHeader(ac->T("Unlocked achievements")));
for (auto achievement : unlockedAchievements) {
achievements->Add(new AchievementView(achievement));
for (auto &achievement : unlockedAchievements) {
achievements->Add(new AchievementView(std::move(achievement)));
}
achievements->Add(new ItemHeader(ac->T("Locked achievements")));
for (auto achievement : lockedAchievements) {
achievements->Add(new AchievementView(achievement));
for (auto &achievement : lockedAchievements) {
achievements->Add(new AchievementView(std::move(achievement)));
}
}
void RetroAchievementsListScreen::CreateLeaderboardsTab(UI::ViewGroup *viewGroup) {
auto di = GetI18NCategory(I18NCat::DIALOG);
auto ac = GetI18NCategory(I18NCat::ACHIEVEMENTS);
using namespace UI;
viewGroup->Add(new GameAchievementSummaryView(Achievements::GetGameID()));
viewGroup->Add(new ItemHeader(ac->T("Leaderboards")));
std::vector<Achievements::Leaderboard> leaderboards;
Achievements::EnumerateLeaderboards([&](const Achievements::Leaderboard &leaderboard) {
leaderboards.push_back(leaderboard);
return true;
});
for (auto &leaderboard : leaderboards) {
if (!leaderboard.hidden) {
int leaderboardID = leaderboard.id;
viewGroup->Add(new LeaderboardSummaryView(std::move(leaderboard)))->OnClick.Add([=](UI::EventParams &e) -> UI::EventReturn {
screenManager()->push(new RetroAchievementsLeaderboardScreen(gamePath_, leaderboardID));
return UI::EVENT_DONE;
});
}
}
}
void RetroAchievementsListScreen::CreateStatisticsTab(UI::ViewGroup *viewGroup) {
auto ac = GetI18NCategory(I18NCat::ACHIEVEMENTS);
using namespace UI;
Achievements::Statistics stats = Achievements::GetStatistics();
viewGroup->Add(new ItemHeader(ac->T("Statistics")));
viewGroup->Add(new InfoItem(ac->T("Bad memory accesses"), StringFromFormat("%d", stats.badMemoryAccessCount)));
}
void RetroAchievementsLeaderboardScreen::CreateTabs() {
auto ac = GetI18NCategory(I18NCat::ACHIEVEMENTS);
const Achievements::Leaderboard *leaderboard = Achievements::GetLeaderboardByID(leaderboardID_);
using namespace UI;
UI::LinearLayout *layout = AddTab("AchievementsLeaderboard", leaderboard->title.c_str());
layout->Add(new TextView(leaderboard->description));
layout->Add(new ItemHeader(ac->T("Leaderboard")));
Poll();
// TODO: Make it pretty.
for (auto &entry : entries_) {
layout->Add(new TextView(StringFromFormat(" %d: %s: %s%s", entry.rank, entry.user.c_str(), entry.formatted_score.c_str(), entry.is_self ? " <<<<< " : "")));
}
}
void RetroAchievementsLeaderboardScreen::Poll() {
if (done_)
return;
std::optional<bool> result = Achievements::TryEnumerateLeaderboardEntries(leaderboardID_, [&](const Achievements::LeaderboardEntry &entry) -> bool {
entries_.push_back(entry);
return true;
});
if (result.has_value()) {
done_ = true;
RecreateViews();
}
}
void RetroAchievementsLeaderboardScreen::update() {
TabbedUIDialogScreenWithGameBackground::update();
Poll();
}
void RetroAchievementsSettingsScreen::CreateTabs() {
auto ac = GetI18NCategory(I18NCat::ACHIEVEMENTS);
auto di = GetI18NCategory(I18NCat::DIALOG);
using namespace UI;
LinearLayout *account = AddTab("AchievementsAccount", ac->T("Account"));
CreateAccountTab(account);
LinearLayout *settings = AddTab("AchievementsSettings", di->T("Settings"));
CreateSettingsTab(settings);
CreateAccountTab(AddTab("AchievementsAccount", ac->T("Account")));
CreateSettingsTab(AddTab("AchievementsSettings", di->T("Settings")));
}
void RetroAchievementsSettingsScreen::sendMessage(const char *message, const char *value) {
@ -88,7 +175,7 @@ void RetroAchievementsSettingsScreen::CreateAccountTab(UI::ViewGroup *viewGroup)
if (parts.size() == 2 && !parts[0].empty() && !parts[1].empty()) {
Achievements::LoginAsync(parts[0].c_str(), parts[1].c_str());
}
});
});
return UI::EVENT_DONE;
});
} else {
@ -146,6 +233,11 @@ void MeasureGameAchievementSummary(const UIContext &dc, int gameID, float *w, fl
*h = 72.0f;
}
void MeasureLeaderboardSummary(const UIContext &dc, const Achievements::Leaderboard &achievement, float *w, float *h) {
*w = 0.0f;
*h = 72.0f;
}
// Graphical
void RenderAchievement(UIContext &dc, const Achievements::Achievement &achievement, AchievementRenderStyle style, const Bounds &bounds, float alpha, float startTime, float time_s) {
using namespace UI;
@ -229,6 +321,45 @@ void RenderGameAchievementSummary(UIContext &dc, int gameID, const Bounds &bound
dc.RebindTexture();
}
void RenderLeaderboardSummary(UIContext &dc, const Achievements::Leaderboard &leaderboard, AchievementRenderStyle style, const Bounds &bounds, float alpha, float startTime, float time_s) {
using namespace UI;
UI::Drawable background = UI::Drawable(dc.theme->backgroundColor);
background.color = colorAlpha(background.color, alpha);
uint32_t fgColor = colorAlpha(dc.theme->itemStyle.fgColor, alpha);
if (style == AchievementRenderStyle::UNLOCKED) {
float mixWhite = pow(Clamp((float)(1.0f - (time_s - startTime)), 0.0f, 1.0f), 3.0f);
background.color = colorBlend(0xFFE0FFFF, background.color, mixWhite);
}
float iconSpace = 64.0f;
dc.Flush();
dc.Begin();
dc.FillRect(background, bounds);
dc.SetFontStyle(dc.theme->uiFont);
dc.SetFontScale(1.0f, 1.0f);
dc.DrawTextRect(leaderboard.title.c_str(), bounds.Inset(iconSpace + 12.0f, 2.0f, 5.0f, 5.0f), fgColor, ALIGN_TOPLEFT);
dc.SetFontScale(0.66f, 0.66f);
dc.DrawTextRect(leaderboard.description.c_str(), bounds.Inset(iconSpace + 12.0f, 39.0f, 5.0f, 5.0f), fgColor, ALIGN_TOPLEFT);
/*
char temp[64];
snprintf(temp, sizeof(temp), "%d", leaderboard.points);
dc.SetFontScale(1.5f, 1.5f);
dc.DrawTextRect(temp, bounds.Expand(-5.0f, -5.0f), fgColor, ALIGN_RIGHT | ALIGN_VCENTER);
dc.SetFontScale(1.0f, 1.0f);
dc.Flush();
*/
dc.Flush();
dc.RebindTexture();
}
void AchievementView::Draw(UIContext &dc) {
RenderAchievement(dc, achievement_, AchievementRenderStyle::LISTED, bounds_, 1.0f, 0.0f, 0.0f);
@ -245,6 +376,14 @@ void AchievementView::Click() {
#endif
}
void LeaderboardSummaryView::Draw(UIContext &dc) {
RenderLeaderboardSummary(dc, leaderboard_, AchievementRenderStyle::LISTED, bounds_, 1.0f, 0.0f, 0.0f);
}
void LeaderboardSummaryView::GetContentDimensions(const UIContext &dc, float &w, float &h) const {
MeasureLeaderboardSummary(dc, leaderboard_, &w, &h);
}
void GameAchievementSummaryView::Draw(UIContext &dc) {
RenderGameAchievementSummary(dc, gameID_, bounds_, 1.0f);
}

View File

@ -18,6 +18,11 @@ public:
protected:
bool ShowSearchControls() const override { return false; }
private:
void CreateAchievementsTab(UI::ViewGroup *viewGroup);
void CreateLeaderboardsTab(UI::ViewGroup *viewGroup);
void CreateStatisticsTab(UI::ViewGroup *viewGroup);
};
// Lets you manage your account, and shows some achievement stats and stuff.
@ -40,6 +45,24 @@ private:
std::string password_;
};
class RetroAchievementsLeaderboardScreen : public TabbedUIDialogScreenWithGameBackground {
public:
RetroAchievementsLeaderboardScreen(const Path &gamePath, int leaderboardID) : TabbedUIDialogScreenWithGameBackground(gamePath), leaderboardID_(leaderboardID) {}
const char *tag() const override { return "RetroAchievementsLeaderboardScreen"; }
void CreateTabs() override;
void update() override;
protected:
bool ShowSearchControls() const override { return false; }
private:
void Poll();
int leaderboardID_;
bool done_ = false;
std::vector<Achievements::LeaderboardEntry> entries_;
};
class UIContext;
enum class AchievementRenderStyle {
@ -54,7 +77,7 @@ void RenderGameAchievementSummary(UIContext &dc, int gameID, const Bounds &bound
class AchievementView : public UI::ClickableItem {
public:
AchievementView(const Achievements::Achievement &achievement, UI::LayoutParams *layoutParams = nullptr) : UI::ClickableItem(layoutParams), achievement_(achievement) {}
AchievementView(const Achievements::Achievement &&achievement, UI::LayoutParams *layoutParams = nullptr) : UI::ClickableItem(layoutParams), achievement_(achievement) {}
void Click() override;
void Draw(UIContext &dc) override;
@ -63,6 +86,17 @@ private:
Achievements::Achievement achievement_;
};
class LeaderboardSummaryView : public UI::ClickableItem {
public:
LeaderboardSummaryView(const Achievements::Leaderboard &&leaderboard, UI::LayoutParams *layoutParams = nullptr) : UI::ClickableItem(layoutParams), leaderboard_(leaderboard) {}
void Draw(UIContext &dc) override;
void GetContentDimensions(const UIContext &dc, float &w, float &h) const override;
private:
Achievements::Leaderboard leaderboard_;
};
class GameAchievementSummaryView : public UI::Item {
public:
GameAchievementSummaryView(int gameID, UI::LayoutParams *layoutParams = nullptr) : UI::Item(layoutParams), gameID_(gameID) {}

View File

@ -244,12 +244,13 @@ static u32 s_last_queried_lboard = 0;
static u32 s_submitting_lboard_id = 0;
static std::optional<std::vector<Achievements::LeaderboardEntry>> s_lboard_entries;
// TODO: Make this show up somewhere.
static u64 g_badMemoryAccessCount = 0;
static Achievements::Statistics g_stats;
const std::string g_gameIconCachePrefix = "game:";
const std::string g_iconCachePrefix = "badge:";
#define PSP_MEMORY_OFFSET 0x08000000
// TODO: Add an icon cache as a string map. We won't cache achievement icons across sessions, let's just
@ -485,6 +486,9 @@ void Achievements::ClearGameInfo(bool clear_achievements, bool clear_leaderboard
s_game_id = 0;
}
// Reset statistics
g_stats = {};
if (had_game)
Host::OnAchievementsRefreshed();
}
@ -1080,6 +1084,10 @@ void Achievements::DownloadImage(std::string url, std::string cache_filename)
}
}
Achievements::Statistics Achievements::GetStatistics() {
return g_stats;
}
std::string Achievements::GetGameAchievementSummary() {
auto ac = GetI18NCategory(I18NCat::ACHIEVEMENTS);
@ -1275,6 +1283,7 @@ void Achievements::GetPatchesCallback(s32 status_code, std::string content_type,
lboard.title = defn.title;
lboard.description = defn.description;
lboard.format = defn.format;
lboard.hidden = defn.hidden;
s_leaderboards.push_back(std::move(lboard));
const int err = rc_runtime_activate_lboard(&s_rcheevos_runtime, defn.id, defn.definition, nullptr, 0);
@ -2092,7 +2101,7 @@ unsigned Achievements::PeekMemory(unsigned address, unsigned num_bytes, void *ud
if (!Memory::IsValidAddress(address)) {
// Some achievement packs are really, really spammy.
// So we'll just count the bad accesses.
g_badMemoryAccessCount++;
g_stats.badMemoryAccessCount++;
if (g_Config.bAchievementsLogBadMemReads) {
WARN_LOG(G3D, "RetroAchievements PeekMemory: Bad address %08x (%d bytes)", address, num_bytes);

View File

@ -23,6 +23,7 @@ class Path;
class PointerWrap;
namespace Achievements {
enum class AchievementCategory : u8
{
Local = 0,
@ -52,6 +53,7 @@ struct Leaderboard
std::string title;
std::string description;
int format;
bool hidden;
};
struct LeaderboardEntry
@ -63,6 +65,12 @@ struct LeaderboardEntry
bool is_self;
};
struct Statistics
{
// Debug stats
int badMemoryAccessCount;
};
// RAIntegration only exists for Windows, so no point checking it on other platforms.
#ifdef WITH_RAINTEGRATION
@ -148,7 +156,11 @@ u32 GetAchievementCount();
u32 GetMaximumPointsForGame();
u32 GetCurrentPointsForGame();
Statistics GetStatistics();
bool EnumerateLeaderboards(std::function<bool(const Leaderboard &)> callback);
// Unlike most other functions here, this you're supposed to poll until you get a valid std::optional.
std::optional<bool> TryEnumerateLeaderboardEntries(u32 id, std::function<bool(const LeaderboardEntry &)> callback);
const Leaderboard *GetLeaderboardByID(u32 id);
u32 GetLeaderboardCount();