Merge pull request #2277 from CookiePLMonster/leaderboards
RetroAchievements Leaderboards implementation
This commit is contained in:
commit
194d6bf7ff
|
@ -7,9 +7,11 @@
|
|||
extern "C" {
|
||||
#endif
|
||||
|
||||
int rc_url_award_cheevo(char* buffer, size_t size, const char* user_name, const char* login_token, unsigned cheevo_id, int hardcore, const char* game_hash);
|
||||
int rc_url_award_cheevo(char* buffer, size_t size, const char* user_name, const char* login_token, unsigned cheevo_id,
|
||||
int hardcore, const char* game_hash);
|
||||
|
||||
int rc_url_submit_lboard(char* buffer, size_t size, const char* user_name, const char* login_token, unsigned lboard_id, int value);
|
||||
int rc_url_submit_lboard(char* buffer, size_t size, const char* user_name, const char* login_token, unsigned lboard_id,
|
||||
int value);
|
||||
|
||||
int rc_url_get_gameid(char* buffer, size_t size, const char* hash);
|
||||
|
||||
|
@ -21,13 +23,22 @@ int rc_url_login_with_password(char* buffer, size_t size, const char* user_name,
|
|||
|
||||
int rc_url_login_with_token(char* buffer, size_t size, const char* user_name, const char* login_token);
|
||||
|
||||
int rc_url_get_unlock_list(char* buffer, size_t size, const char* user_name, const char* login_token, unsigned gameid, int hardcore);
|
||||
int rc_url_get_unlock_list(char* buffer, size_t size, const char* user_name, const char* login_token, unsigned gameid,
|
||||
int hardcore);
|
||||
|
||||
int rc_url_post_playing(char* buffer, size_t size, const char* user_name, const char* login_token, unsigned gameid);
|
||||
|
||||
int rc_url_ping(char* url_buffer, size_t url_buffer_size, char* post_buffer, size_t post_buffer_size,
|
||||
const char* user_name, const char* login_token, unsigned gameid, const char* rich_presence);
|
||||
|
||||
// Custom exports, static in upstream rcheevos
|
||||
int rc_url_append_unum(char* buffer, size_t buffer_size, size_t* buffer_offset, const char* param, unsigned value);
|
||||
|
||||
int rc_url_append_str(char* buffer, size_t buffer_size, size_t* buffer_offset, const char* param, const char* value);
|
||||
|
||||
int rc_url_build_dorequest(char* url_buffer, size_t url_buffer_size, size_t* buffer_offset, const char* api,
|
||||
const char* user_name);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
|
|
@ -293,7 +293,7 @@ static int rc_url_append_param_equals(char* buffer, size_t buffer_size, size_t b
|
|||
return written + (int)buffer_offset;
|
||||
}
|
||||
|
||||
static int rc_url_append_unum(char* buffer, size_t buffer_size, size_t* buffer_offset, const char* param, unsigned value)
|
||||
int rc_url_append_unum(char* buffer, size_t buffer_size, size_t* buffer_offset, const char* param, unsigned value)
|
||||
{
|
||||
int written = rc_url_append_param_equals(buffer, buffer_size, *buffer_offset, param);
|
||||
if (written > 0) {
|
||||
|
@ -311,7 +311,7 @@ static int rc_url_append_unum(char* buffer, size_t buffer_size, size_t* buffer_o
|
|||
return -1;
|
||||
}
|
||||
|
||||
static int rc_url_append_str(char* buffer, size_t buffer_size, size_t* buffer_offset, const char* param, const char* value)
|
||||
int rc_url_append_str(char* buffer, size_t buffer_size, size_t* buffer_offset, const char* param, const char* value)
|
||||
{
|
||||
int written = rc_url_append_param_equals(buffer, buffer_size, *buffer_offset, param);
|
||||
if (written > 0)
|
||||
|
@ -330,7 +330,7 @@ static int rc_url_append_str(char* buffer, size_t buffer_size, size_t* buffer_of
|
|||
return -1;
|
||||
}
|
||||
|
||||
static int rc_url_build_dorequest(char* url_buffer, size_t url_buffer_size, size_t* buffer_offset,
|
||||
int rc_url_build_dorequest(char* url_buffer, size_t url_buffer_size, size_t* buffer_offset,
|
||||
const char* api, const char* user_name)
|
||||
{
|
||||
const char* base_url = "https://retroachievements.org/dorequest.php";
|
||||
|
|
|
@ -73,11 +73,15 @@ static std::string s_game_publisher;
|
|||
static std::string s_game_release_date;
|
||||
static std::string s_game_icon;
|
||||
static std::vector<Achievement> s_achievements;
|
||||
static std::vector<Leaderboard> s_leaderboards;
|
||||
|
||||
static bool s_has_rich_presence = false;
|
||||
static std::string s_rich_presence_string;
|
||||
static Common::Timer s_last_ping_time;
|
||||
|
||||
static u32 s_last_queried_lboard;
|
||||
static std::optional<std::vector<LeaderboardEntry>> s_lboard_entries;
|
||||
|
||||
static u32 s_total_image_downloads;
|
||||
static u32 s_completed_image_downloads;
|
||||
static bool s_image_download_progress_active;
|
||||
|
@ -721,6 +725,44 @@ static void GetPatchesCallback(s32 status_code, const FrontendCommon::HTTPDownlo
|
|||
}
|
||||
}
|
||||
|
||||
// parse leaderboards
|
||||
if (patch_data.HasMember("Leaderboards") && patch_data["Leaderboards"].IsArray())
|
||||
{
|
||||
const auto leaderboards(patch_data["Leaderboards"].GetArray());
|
||||
for (const auto& leaderboard : leaderboards)
|
||||
{
|
||||
if (!leaderboard.HasMember("ID") || !leaderboard["ID"].IsNumber() || !leaderboard.HasMember("Mem") ||
|
||||
!leaderboard["Mem"].IsString() || !leaderboard.HasMember("Title") || !leaderboard["Title"].IsString() ||
|
||||
!leaderboard.HasMember("Format") || !leaderboard["Format"].IsString())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
const unsigned int id = leaderboard["ID"].GetUint();
|
||||
const char* title = leaderboard["Title"].GetString();
|
||||
const char* memaddr = leaderboard["Mem"].GetString();
|
||||
const char* format = leaderboard["Format"].GetString();
|
||||
std::string description = GetOptionalString(leaderboard, "Description");
|
||||
|
||||
Leaderboard lboard;
|
||||
lboard.id = id;
|
||||
lboard.title = title;
|
||||
lboard.description = std::move(description);
|
||||
lboard.format = rc_parse_format(format);
|
||||
s_leaderboards.push_back(std::move(lboard));
|
||||
|
||||
const int err = rc_runtime_activate_lboard(&s_rcheevos_runtime, id, memaddr, nullptr, 0);
|
||||
if (err != RC_OK)
|
||||
{
|
||||
Log_ErrorPrintf("Leaderboard %u memaddr parse error: %s", id, rc_error_str(err));
|
||||
}
|
||||
else
|
||||
{
|
||||
Log_DevPrintf("Activated leaderboard %s (%u)", title, id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// parse rich presence
|
||||
if (s_rich_presence_enabled && patch_data.HasMember("RichPresencePatch") &&
|
||||
patch_data["RichPresencePatch"].IsString())
|
||||
|
@ -759,6 +801,69 @@ static void GetPatchesCallback(s32 status_code, const FrontendCommon::HTTPDownlo
|
|||
}
|
||||
}
|
||||
|
||||
static void GetLbInfoCallback(s32 status_code, const FrontendCommon::HTTPDownloader::Request::Data& data)
|
||||
{
|
||||
rapidjson::Document doc;
|
||||
if (!ParseResponseJSON("Get Leaderboard Info", status_code, data, doc))
|
||||
return;
|
||||
|
||||
if (!doc.HasMember("LeaderboardData") || !doc["LeaderboardData"].IsObject())
|
||||
{
|
||||
FormattedError("No leaderboard returned from server.");
|
||||
return;
|
||||
}
|
||||
|
||||
// parse info
|
||||
const auto lb_data(doc["LeaderboardData"].GetObject());
|
||||
if (!lb_data["LBID"].IsUint())
|
||||
{
|
||||
FormattedError("Leaderboard data is missing leadeboard ID");
|
||||
return;
|
||||
}
|
||||
|
||||
const u32 lbid = lb_data["LBID"].GetUint();
|
||||
if (lbid != s_last_queried_lboard)
|
||||
{
|
||||
// User has already requested another leaderboard, drop this data
|
||||
return;
|
||||
}
|
||||
|
||||
if (lb_data.HasMember("Entries") && lb_data["Entries"].IsArray())
|
||||
{
|
||||
const Leaderboard* leaderboard = GetLeaderboardByID(lbid);
|
||||
if (leaderboard == nullptr)
|
||||
{
|
||||
Log_ErrorPrintf("Attempting to list unknown leaderboard %u", lbid);
|
||||
return;
|
||||
}
|
||||
|
||||
std::vector<LeaderboardEntry> entries;
|
||||
|
||||
const auto lb_entries(lb_data["Entries"].GetArray());
|
||||
for (const auto& entry : lb_entries)
|
||||
{
|
||||
if (!entry.HasMember("User") || !entry["User"].IsString() || !entry.HasMember("Score") ||
|
||||
!entry["Score"].IsNumber() || !entry.HasMember("Rank") || !entry["Rank"].IsNumber())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
char score[128];
|
||||
rc_format_value(score, sizeof(score), entry["Score"].GetInt(), leaderboard->format);
|
||||
|
||||
LeaderboardEntry lbe;
|
||||
lbe.user = entry["User"].GetString();
|
||||
lbe.rank = entry["Rank"].GetUint();
|
||||
lbe.formatted_score = score;
|
||||
lbe.is_self = lbe.user == s_username;
|
||||
|
||||
entries.push_back(std::move(lbe));
|
||||
}
|
||||
|
||||
s_lboard_entries = std::move(entries);
|
||||
}
|
||||
}
|
||||
|
||||
static void GetPatches(u32 game_id)
|
||||
{
|
||||
char url[512];
|
||||
|
@ -1035,6 +1140,72 @@ u32 GetCurrentPointsForGame()
|
|||
return points;
|
||||
}
|
||||
|
||||
bool EnumerateLeaderboards(std::function<bool(const Leaderboard&)> callback)
|
||||
{
|
||||
for (const Leaderboard& lboard : s_leaderboards)
|
||||
{
|
||||
if (!callback(lboard))
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
std::optional<bool> TryEnumerateLeaderboardEntries(u32 id, std::function<bool(const LeaderboardEntry&)> callback)
|
||||
{
|
||||
if (id == s_last_queried_lboard)
|
||||
{
|
||||
if (s_lboard_entries)
|
||||
{
|
||||
for (const LeaderboardEntry& entry : *s_lboard_entries)
|
||||
{
|
||||
if (!callback(entry))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// TODO: Add paging? For now, stick to defaults
|
||||
char url[512];
|
||||
|
||||
size_t written = 0;
|
||||
rc_url_build_dorequest(url, sizeof(url), &written, "lbinfo", s_username.c_str());
|
||||
rc_url_append_unum(url, sizeof(url), &written, "i", id);
|
||||
rc_url_append_unum(url, sizeof(url), &written, "c",
|
||||
15); // Just over what a single page can store, should be a reasonable amount for now
|
||||
// rc_url_append_unum(url, sizeof(url), &written, "o", 0);
|
||||
|
||||
s_last_queried_lboard = id;
|
||||
s_lboard_entries.reset();
|
||||
s_http_downloader->CreateRequest(url, GetLbInfoCallback);
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
const Leaderboard* GetLeaderboardByID(u32 id)
|
||||
{
|
||||
for (const Leaderboard& lb : s_leaderboards)
|
||||
{
|
||||
if (lb.id == id)
|
||||
return &lb;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
u32 GetLeaderboardCount()
|
||||
{
|
||||
return static_cast<u32>(s_leaderboards.size());
|
||||
}
|
||||
|
||||
bool IsLeaderboardTimeType(const Leaderboard& leaderboard)
|
||||
{
|
||||
return leaderboard.format != RC_FORMAT_SCORE && leaderboard.format != RC_FORMAT_VALUE;
|
||||
}
|
||||
|
||||
void ActivateLockedAchievements()
|
||||
{
|
||||
for (Achievement& cheevo : s_achievements)
|
||||
|
@ -1083,6 +1254,12 @@ static void UnlockAchievementCallback(s32 status_code, const FrontendCommon::HTT
|
|||
// we don't really need to do anything here
|
||||
}
|
||||
|
||||
static void SubmitLeaderboardCallback(s32 status_code, const FrontendCommon::HTTPDownloader::Request::Data& data)
|
||||
{
|
||||
// Force the next leaderboard query to repopulate everything, just in case the user wants to see their new score
|
||||
s_last_queried_lboard = 0;
|
||||
}
|
||||
|
||||
void UnlockAchievement(u32 achievement_id, bool add_notification /* = true*/)
|
||||
{
|
||||
Achievement* achievement = GetAchievementByID(achievement_id);
|
||||
|
@ -1138,6 +1315,19 @@ void UnlockAchievement(u32 achievement_id, bool add_notification /* = true*/)
|
|||
s_http_downloader->CreateRequest(url, UnlockAchievementCallback);
|
||||
}
|
||||
|
||||
void SubmitLeaderboard(u32 leaderboard_id, int value)
|
||||
{
|
||||
if (s_test_mode)
|
||||
{
|
||||
Log_WarningPrintf("Skipping sending leaderboard %u result to server because of test mode.", leaderboard_id);
|
||||
return;
|
||||
}
|
||||
|
||||
char url[512];
|
||||
rc_url_submit_lboard(url, sizeof(url), s_username.c_str(), s_login_token.c_str(), leaderboard_id, value);
|
||||
s_http_downloader->CreateRequest(url, SubmitLeaderboardCallback);
|
||||
}
|
||||
|
||||
void CheevosEventHandler(const rc_runtime_event_t* runtime_event)
|
||||
{
|
||||
static const char* events[] = {"RC_RUNTIME_EVENT_ACHIEVEMENT_ACTIVATED", "RC_RUNTIME_EVENT_ACHIEVEMENT_PAUSED",
|
||||
|
@ -1152,6 +1342,8 @@ void CheevosEventHandler(const rc_runtime_event_t* runtime_event)
|
|||
|
||||
if (runtime_event->type == RC_RUNTIME_EVENT_ACHIEVEMENT_TRIGGERED)
|
||||
UnlockAchievement(runtime_event->id);
|
||||
else if (runtime_event->type == RC_RUNTIME_EVENT_LBOARD_TRIGGERED)
|
||||
SubmitLeaderboard(runtime_event->id, runtime_event->value);
|
||||
}
|
||||
|
||||
// from cheats.cpp - do we want to move this somewhere else?
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
#pragma once
|
||||
#include "core/types.h"
|
||||
#include <functional>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
|
||||
class CDImage;
|
||||
|
@ -28,6 +29,22 @@ struct Achievement
|
|||
bool active;
|
||||
};
|
||||
|
||||
struct Leaderboard
|
||||
{
|
||||
u32 id;
|
||||
std::string title;
|
||||
std::string description;
|
||||
int format;
|
||||
};
|
||||
|
||||
struct LeaderboardEntry
|
||||
{
|
||||
std::string user;
|
||||
std::string formatted_score;
|
||||
u32 rank;
|
||||
bool is_self;
|
||||
};
|
||||
|
||||
extern bool g_active;
|
||||
extern bool g_challenge_mode;
|
||||
extern u32 g_game_id;
|
||||
|
@ -90,6 +107,13 @@ u32 GetAchievementCount();
|
|||
u32 GetMaximumPointsForGame();
|
||||
u32 GetCurrentPointsForGame();
|
||||
|
||||
bool EnumerateLeaderboards(std::function<bool(const Leaderboard&)> callback);
|
||||
std::optional<bool> TryEnumerateLeaderboardEntries(u32 id, std::function<bool(const LeaderboardEntry&)> callback);
|
||||
const Leaderboard* GetLeaderboardByID(u32 id);
|
||||
u32 GetLeaderboardCount();
|
||||
bool IsLeaderboardTimeType(const Leaderboard& leaderboard);
|
||||
|
||||
void UnlockAchievement(u32 achievement_id, bool add_notification = true);
|
||||
void SubmitLeaderboard(u32 leaderboard_id, int value);
|
||||
|
||||
} // namespace Cheevos
|
||||
|
|
|
@ -85,6 +85,7 @@ static void ReturnToMainWindow();
|
|||
static void DrawLandingWindow();
|
||||
static void DrawQuickMenu(MainWindowType type);
|
||||
static void DrawAchievementWindow();
|
||||
static void DrawLeaderboardsWindow();
|
||||
static void DrawDebugMenu();
|
||||
static void DrawStatsOverlay();
|
||||
static void DrawOSDMessages();
|
||||
|
@ -112,6 +113,7 @@ static bool s_quick_menu_was_open = false;
|
|||
static bool s_was_paused_on_quick_menu_open = false;
|
||||
static bool s_about_window_open = false;
|
||||
static u32 s_close_button_state = 0;
|
||||
static std::optional<u32> s_open_leaderboard_id;
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
// Resources
|
||||
|
@ -343,6 +345,9 @@ void Render()
|
|||
case MainWindowType::Achievements:
|
||||
DrawAchievementWindow();
|
||||
break;
|
||||
case MainWindowType::Leaderboards:
|
||||
DrawLeaderboardsWindow();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
@ -2531,7 +2536,7 @@ void DrawQuickMenu(MainWindowType type)
|
|||
if (BeginFullscreenWindow(window_pos, window_size, "pause_menu", ImVec4(0.0f, 0.0f, 0.0f, 0.0f), 0.0f, 10.0f,
|
||||
ImGuiWindowFlags_NoBackground))
|
||||
{
|
||||
BeginMenuButtons(12, 1.0f, ImGuiFullscreen::LAYOUT_MENU_BUTTON_X_PADDING,
|
||||
BeginMenuButtons(13, 1.0f, ImGuiFullscreen::LAYOUT_MENU_BUTTON_X_PADDING,
|
||||
ImGuiFullscreen::LAYOUT_MENU_BUTTON_Y_PADDING,
|
||||
ImGuiFullscreen::LAYOUT_MENU_BUTTON_HEIGHT_NO_SUMMARY);
|
||||
|
||||
|
@ -2550,8 +2555,16 @@ void DrawQuickMenu(MainWindowType type)
|
|||
if (ActiveButton(ICON_FA_TROPHY " Achievements", false, achievements_enabled))
|
||||
s_current_main_window = MainWindowType::Achievements;
|
||||
|
||||
const bool leaderboards_enabled = Cheevos::HasActiveGame() && (Cheevos::GetLeaderboardCount() > 0);
|
||||
if (ActiveButton(ICON_FA_STOPWATCH " Leaderboards", false, leaderboards_enabled))
|
||||
{
|
||||
s_current_main_window = MainWindowType::Leaderboards;
|
||||
s_open_leaderboard_id.reset();
|
||||
}
|
||||
|
||||
#else
|
||||
ActiveButton(ICON_FA_TROPHY " Achievements", false, false);
|
||||
ActiveButton(ICON_FA_STOPWATCH " Leaderboards", false, false);
|
||||
#endif
|
||||
|
||||
if (ActiveButton(ICON_FA_CAMERA " Save Screenshot", false))
|
||||
|
@ -4259,9 +4272,342 @@ void DrawAchievementWindow()
|
|||
EndFullscreenWindow();
|
||||
}
|
||||
|
||||
static void DrawLeaderboardListEntry(const Cheevos::Leaderboard& lboard)
|
||||
{
|
||||
static constexpr float alpha = 0.8f;
|
||||
|
||||
TinyString id_str;
|
||||
id_str.Format("%u", lboard.id);
|
||||
|
||||
ImRect bb;
|
||||
bool visible, hovered;
|
||||
bool pressed =
|
||||
MenuButtonFrame(id_str, true, LAYOUT_MENU_BUTTON_HEIGHT, &visible, &hovered, &bb.Min, &bb.Max, 0, alpha);
|
||||
if (!visible)
|
||||
return;
|
||||
|
||||
const float midpoint = bb.Min.y + g_large_font->FontSize + LayoutScale(4.0f);
|
||||
const float text_start_x = bb.Min.x + LayoutScale(15.0f);
|
||||
const ImRect title_bb(ImVec2(text_start_x, bb.Min.y), ImVec2(bb.Max.x, midpoint));
|
||||
const ImRect summary_bb(ImVec2(text_start_x, midpoint), bb.Max);
|
||||
|
||||
ImGui::PushFont(g_large_font);
|
||||
ImGui::RenderTextClipped(title_bb.Min, title_bb.Max, lboard.title.c_str(), lboard.title.c_str() + lboard.title.size(),
|
||||
nullptr, ImVec2(0.0f, 0.0f), &title_bb);
|
||||
ImGui::PopFont();
|
||||
|
||||
if (!lboard.description.empty())
|
||||
{
|
||||
ImGui::PushFont(g_medium_font);
|
||||
ImGui::RenderTextClipped(summary_bb.Min, summary_bb.Max, lboard.description.c_str(),
|
||||
lboard.description.c_str() + lboard.description.size(), nullptr, ImVec2(0.0f, 0.0f),
|
||||
&summary_bb);
|
||||
ImGui::PopFont();
|
||||
}
|
||||
|
||||
if (pressed)
|
||||
{
|
||||
s_open_leaderboard_id = lboard.id;
|
||||
}
|
||||
}
|
||||
|
||||
static void DrawLeaderboardEntry(const Cheevos::LeaderboardEntry& lbEntry, float rank_column_width,
|
||||
float name_column_width, float column_spacing)
|
||||
{
|
||||
static constexpr float alpha = 0.8f;
|
||||
|
||||
ImRect bb;
|
||||
bool visible, hovered;
|
||||
bool pressed = MenuButtonFrame(lbEntry.user.c_str(), true, LAYOUT_MENU_BUTTON_HEIGHT_NO_SUMMARY, &visible, &hovered,
|
||||
&bb.Min, &bb.Max, 0, alpha);
|
||||
if (!visible)
|
||||
return;
|
||||
|
||||
const float spacing = LayoutScale(10.0f);
|
||||
const float midpoint = bb.Min.y + g_large_font->FontSize + LayoutScale(4.0f);
|
||||
float text_start_x = bb.Min.x + LayoutScale(15.0f);
|
||||
SmallString text;
|
||||
|
||||
text.Format("%u", lbEntry.rank);
|
||||
|
||||
ImGui::PushFont(g_large_font);
|
||||
if (lbEntry.is_self)
|
||||
{
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(255, 242, 0, 255));
|
||||
}
|
||||
|
||||
const ImRect rank_bb(ImVec2(text_start_x, bb.Min.y), ImVec2(bb.Max.x, midpoint));
|
||||
ImGui::RenderTextClipped(rank_bb.Min, rank_bb.Max, text.GetCharArray(), text.GetCharArray() + text.GetLength(),
|
||||
nullptr, ImVec2(0.0f, 0.0f), &rank_bb);
|
||||
text_start_x += rank_column_width + column_spacing;
|
||||
|
||||
const ImRect user_bb(ImVec2(text_start_x, bb.Min.y), ImVec2(bb.Max.x, midpoint));
|
||||
ImGui::RenderTextClipped(user_bb.Min, user_bb.Max, lbEntry.user.c_str(), lbEntry.user.c_str() + lbEntry.user.size(),
|
||||
nullptr, ImVec2(0.0f, 0.0f), &user_bb);
|
||||
text_start_x += name_column_width + column_spacing;
|
||||
|
||||
const ImRect score_bb(ImVec2(text_start_x, bb.Min.y), ImVec2(bb.Max.x, midpoint));
|
||||
ImGui::RenderTextClipped(score_bb.Min, score_bb.Max, lbEntry.formatted_score.c_str(),
|
||||
lbEntry.formatted_score.c_str() + lbEntry.formatted_score.size(), nullptr,
|
||||
ImVec2(0.0f, 0.0f), &score_bb);
|
||||
|
||||
if (lbEntry.is_self)
|
||||
{
|
||||
ImGui::PopStyleColor();
|
||||
}
|
||||
|
||||
ImGui::PopFont();
|
||||
|
||||
// This API DOES list the submission date/time, but is it relevant?
|
||||
#if 0
|
||||
if (!cheevo.locked)
|
||||
{
|
||||
ImGui::PushFont(g_medium_font);
|
||||
|
||||
const ImRect time_bb(ImVec2(text_start_x, bb.Min.y),
|
||||
ImVec2(bb.Max.x, bb.Min.y + g_medium_font->FontSize + LayoutScale(4.0f)));
|
||||
text.Format("Unlocked 21 Feb, 2019 @ 3:14am");
|
||||
ImGui::RenderTextClipped(time_bb.Min, time_bb.Max, text.GetCharArray(), text.GetCharArray() + text.GetLength(),
|
||||
nullptr, ImVec2(1.0f, 0.0f), &time_bb);
|
||||
ImGui::PopFont();
|
||||
}
|
||||
#endif
|
||||
|
||||
if (pressed)
|
||||
{
|
||||
// Anything?
|
||||
}
|
||||
}
|
||||
|
||||
void DrawLeaderboardsWindow()
|
||||
{
|
||||
static constexpr float alpha = 0.8f;
|
||||
static constexpr float heading_height_unscaled = 110.0f;
|
||||
|
||||
ImGui::SetNextWindowBgAlpha(alpha);
|
||||
|
||||
const bool is_leaderboard_open = s_open_leaderboard_id.has_value();
|
||||
bool close_leaderboard_on_exit = false;
|
||||
|
||||
const ImVec4 background(0.13f, 0.13f, 0.13f, alpha);
|
||||
const ImVec2 display_size(ImGui::GetIO().DisplaySize);
|
||||
const float padding = LayoutScale(10.0f);
|
||||
const float spacing = LayoutScale(10.0f);
|
||||
float heading_height = LayoutScale(heading_height_unscaled);
|
||||
if (is_leaderboard_open)
|
||||
{
|
||||
// Add space for a legend - spacing + 1 line of text + spacing + line
|
||||
heading_height += spacing + LayoutScale(LAYOUT_MENU_BUTTON_HEIGHT_NO_SUMMARY) + spacing;
|
||||
}
|
||||
|
||||
const float rank_column_width =
|
||||
g_large_font->CalcTextSizeA(g_large_font->FontSize, std::numeric_limits<float>::max(), -1.0f, "99999").x;
|
||||
const float name_column_width =
|
||||
g_large_font
|
||||
->CalcTextSizeA(g_large_font->FontSize, std::numeric_limits<float>::max(), -1.0f, "WWWWWWWWWWWWWWWWWWWW")
|
||||
.x;
|
||||
const float column_spacing = spacing * 2.0f;
|
||||
|
||||
if (BeginFullscreenWindow(
|
||||
ImVec2(0.0f, 0.0f), ImVec2(display_size.x, heading_height), "leaderboards_heading", background, 0.0f, 0.0f,
|
||||
ImGuiWindowFlags_NoNav | ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoScrollWithMouse))
|
||||
{
|
||||
ImRect bb;
|
||||
bool visible, hovered;
|
||||
bool pressed = MenuButtonFrame("leaderboards_heading", false, heading_height_unscaled, &visible, &hovered, &bb.Min,
|
||||
&bb.Max, 0, alpha);
|
||||
UNREFERENCED_VARIABLE(pressed);
|
||||
|
||||
if (visible)
|
||||
{
|
||||
const float image_height = LayoutScale(85.0f);
|
||||
|
||||
const ImVec2 icon_min(bb.Min + ImVec2(padding, padding));
|
||||
const ImVec2 icon_max(icon_min + ImVec2(image_height, image_height));
|
||||
|
||||
const std::string& icon_path = Cheevos::GetGameIcon();
|
||||
if (!icon_path.empty())
|
||||
{
|
||||
HostDisplayTexture* badge = GetCachedTexture(icon_path);
|
||||
if (badge)
|
||||
{
|
||||
ImGui::GetWindowDrawList()->AddImage(badge->GetHandle(), icon_min, icon_max, ImVec2(0.0f, 0.0f),
|
||||
ImVec2(1.0f, 1.0f), IM_COL32(255, 255, 255, 255));
|
||||
}
|
||||
}
|
||||
|
||||
float left = bb.Min.x + padding + image_height + spacing;
|
||||
float right = bb.Max.x - padding;
|
||||
float top = bb.Min.y + padding;
|
||||
ImDrawList* dl = ImGui::GetWindowDrawList();
|
||||
SmallString text;
|
||||
ImVec2 text_size;
|
||||
|
||||
const u32 leaderboard_count = Cheevos::GetLeaderboardCount();
|
||||
|
||||
if (!is_leaderboard_open)
|
||||
{
|
||||
if (FloatingButton(ICON_FA_WINDOW_CLOSE, 10.0f, 10.0f, -1.0f, -1.0f, 1.0f, 0.0f, true, g_large_font) ||
|
||||
WantsToCloseMenu())
|
||||
{
|
||||
ReturnToMainWindow();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (FloatingButton(ICON_FA_CARET_SQUARE_LEFT, 10.0f, 10.0f, -1.0f, -1.0f, 1.0f, 0.0f, true, g_large_font) ||
|
||||
WantsToCloseMenu())
|
||||
{
|
||||
close_leaderboard_on_exit = true;
|
||||
}
|
||||
}
|
||||
|
||||
const ImRect title_bb(ImVec2(left, top), ImVec2(right, top + g_large_font->FontSize));
|
||||
text.Assign(Cheevos::GetGameTitle());
|
||||
|
||||
top += g_large_font->FontSize + spacing;
|
||||
|
||||
ImGui::PushFont(g_large_font);
|
||||
ImGui::RenderTextClipped(title_bb.Min, title_bb.Max, text.GetCharArray(), text.GetCharArray() + text.GetLength(),
|
||||
nullptr, ImVec2(0.0f, 0.0f), &title_bb);
|
||||
ImGui::PopFont();
|
||||
|
||||
if (s_open_leaderboard_id)
|
||||
{
|
||||
const Cheevos::Leaderboard* lboard = Cheevos::GetLeaderboardByID(*s_open_leaderboard_id);
|
||||
if (lboard != nullptr)
|
||||
{
|
||||
const ImRect subtitle_bb(ImVec2(left, top), ImVec2(right, top + g_large_font->FontSize));
|
||||
text.Assign(lboard->title);
|
||||
|
||||
top += g_large_font->FontSize + spacing;
|
||||
|
||||
ImGui::PushFont(g_large_font);
|
||||
ImGui::RenderTextClipped(subtitle_bb.Min, subtitle_bb.Max, text.GetCharArray(),
|
||||
text.GetCharArray() + text.GetLength(), nullptr, ImVec2(0.0f, 0.0f), &subtitle_bb);
|
||||
ImGui::PopFont();
|
||||
|
||||
text.Assign(lboard->description);
|
||||
}
|
||||
else
|
||||
{
|
||||
text.Clear();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
text.Format("This game has %u leaderboards.", leaderboard_count);
|
||||
}
|
||||
|
||||
const ImRect summary_bb(ImVec2(left, top), ImVec2(right, top + g_medium_font->FontSize));
|
||||
top += g_medium_font->FontSize + spacing;
|
||||
|
||||
ImGui::PushFont(g_medium_font);
|
||||
ImGui::RenderTextClipped(summary_bb.Min, summary_bb.Max, text.GetCharArray(),
|
||||
text.GetCharArray() + text.GetLength(), nullptr, ImVec2(0.0f, 0.0f), &summary_bb);
|
||||
ImGui::PopFont();
|
||||
}
|
||||
|
||||
if (is_leaderboard_open)
|
||||
{
|
||||
pressed = MenuButtonFrame("legend", false, LAYOUT_MENU_BUTTON_HEIGHT_NO_SUMMARY, &visible, &hovered, &bb.Min,
|
||||
&bb.Max, 0, alpha);
|
||||
|
||||
UNREFERENCED_VARIABLE(pressed);
|
||||
|
||||
if (visible)
|
||||
{
|
||||
const Cheevos::Leaderboard* lboard = Cheevos::GetLeaderboardByID(*s_open_leaderboard_id);
|
||||
|
||||
const float midpoint = bb.Min.y + g_large_font->FontSize + LayoutScale(4.0f);
|
||||
float text_start_x = bb.Min.x + LayoutScale(15.0f) + padding;
|
||||
|
||||
ImGui::PushFont(g_large_font);
|
||||
|
||||
const ImRect rank_bb(ImVec2(text_start_x, bb.Min.y), ImVec2(bb.Max.x, midpoint));
|
||||
ImGui::RenderTextClipped(rank_bb.Min, rank_bb.Max, "Rank", nullptr, nullptr, ImVec2(0.0f, 0.0f), &rank_bb);
|
||||
text_start_x += rank_column_width + column_spacing;
|
||||
|
||||
const ImRect user_bb(ImVec2(text_start_x, bb.Min.y), ImVec2(bb.Max.x, midpoint));
|
||||
ImGui::RenderTextClipped(user_bb.Min, user_bb.Max, "Name", nullptr, nullptr, ImVec2(0.0f, 0.0f), &user_bb);
|
||||
text_start_x += name_column_width + column_spacing;
|
||||
|
||||
const ImRect score_bb(ImVec2(text_start_x, bb.Min.y), ImVec2(bb.Max.x, midpoint));
|
||||
ImGui::RenderTextClipped(score_bb.Min, score_bb.Max,
|
||||
lboard != nullptr && Cheevos::IsLeaderboardTimeType(*lboard) ? "Time" : "Score",
|
||||
nullptr, nullptr, ImVec2(0.0f, 0.0f), &score_bb);
|
||||
|
||||
ImGui::PopFont();
|
||||
|
||||
const float line_thickness = LayoutScale(1.0f);
|
||||
const float line_padding = LayoutScale(5.0f);
|
||||
const ImVec2 line_start(bb.Min.x, bb.Min.y + g_large_font->FontSize + line_padding);
|
||||
const ImVec2 line_end(bb.Max.x, line_start.y);
|
||||
ImGui::GetWindowDrawList()->AddLine(line_start, line_end, ImGui::GetColorU32(ImGuiCol_TextDisabled),
|
||||
line_thickness);
|
||||
}
|
||||
}
|
||||
}
|
||||
EndFullscreenWindow();
|
||||
|
||||
ImGui::SetNextWindowBgAlpha(alpha);
|
||||
|
||||
if (!is_leaderboard_open)
|
||||
{
|
||||
if (BeginFullscreenWindow(ImVec2(0.0f, heading_height), ImVec2(display_size.x, display_size.y - heading_height),
|
||||
"leaderboards", background, 0.0f, 0.0f, 0))
|
||||
{
|
||||
BeginMenuButtons();
|
||||
|
||||
Cheevos::EnumerateLeaderboards([](const Cheevos::Leaderboard& lboard) -> bool {
|
||||
DrawLeaderboardListEntry(lboard);
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
EndMenuButtons();
|
||||
}
|
||||
EndFullscreenWindow();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (BeginFullscreenWindow(ImVec2(0.0f, heading_height), ImVec2(display_size.x, display_size.y - heading_height),
|
||||
"leaderboard", background, 0.0f, 0.0f, 0))
|
||||
{
|
||||
BeginMenuButtons();
|
||||
|
||||
const auto result = Cheevos::TryEnumerateLeaderboardEntries(
|
||||
*s_open_leaderboard_id,
|
||||
[rank_column_width, name_column_width, column_spacing](const Cheevos::LeaderboardEntry& lbEntry) -> bool {
|
||||
DrawLeaderboardEntry(lbEntry, rank_column_width, name_column_width, column_spacing);
|
||||
return true;
|
||||
});
|
||||
|
||||
if (!result.has_value())
|
||||
{
|
||||
ImGui::PushFont(g_large_font);
|
||||
|
||||
const ImVec2 pos_min(0.0f, heading_height);
|
||||
const ImVec2 pos_max(display_size.x, display_size.y);
|
||||
ImGui::RenderTextClipped(pos_min, pos_max, "Downloading leaderboard data, please wait...", nullptr, nullptr,
|
||||
ImVec2(0.5f, 0.5f));
|
||||
|
||||
ImGui::PopFont();
|
||||
}
|
||||
|
||||
EndMenuButtons();
|
||||
}
|
||||
EndFullscreenWindow();
|
||||
}
|
||||
|
||||
if (close_leaderboard_on_exit)
|
||||
s_open_leaderboard_id.reset();
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
void DrawAchievementWindow() {}
|
||||
void DrawLeaderboardsWindow() {}
|
||||
|
||||
#endif
|
||||
|
||||
|
|
|
@ -19,6 +19,7 @@ enum class MainWindowType
|
|||
Settings,
|
||||
QuickMenu,
|
||||
Achievements,
|
||||
Leaderboards,
|
||||
};
|
||||
|
||||
enum class SettingsPage
|
||||
|
|
Loading…
Reference in New Issue