mirror of
https://github.com/hrydgard/ppsspp.git
synced 2025-03-05 12:47:08 +00:00
Merge pull request #17629 from hrydgard/retroachievements-leaderboards
RetroAchievements: Add function to view leaderboards
This commit is contained in:
commit
5e3faafe01
@ -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());
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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) {}
|
||||
|
@ -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);
|
||||
|
@ -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();
|
||||
|
Loading…
x
Reference in New Issue
Block a user