leaderboard tracker (#740)

* leaderboard tracker

* more

* more

* more

* more
This commit is contained in:
Logan McNaughton 2026-04-13 13:30:18 +02:00 committed by GitHub
parent c4b86eb4b1
commit 595089187d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 111 additions and 25 deletions

View File

@ -7,6 +7,7 @@
#include <SDL3/SDL_vulkan.h>
#include <SDL3_ttf/SDL_ttf.h>
#include <cmath>
#include <map>
using namespace Vulkan;
@ -101,6 +102,8 @@ static TTF_Font *achievement_challenge_indicator_font;
static std::vector<const char *> achievement_challenge_indicators;
static Vulkan::ImageHandle achievement_challenge_indicator_image;
static Vulkan::ImageHandle achievement_progress_indicator_image;
static std::map<uint32_t, std::string> leaderboard_trackers;
static Vulkan::ImageHandle leaderboard_tracker_image;
#define MESSAGE_TIME 3000 // 3 seconds
@ -308,12 +311,15 @@ void rdp_init(void *_window, GFX_INFO _gfx_info, const void *font,
achievement_challenge_indicators.clear();
achievement_challenge_indicator_image = Vulkan::ImageHandle();
achievement_progress_indicator_image = Vulkan::ImageHandle();
leaderboard_trackers.clear();
leaderboard_tracker_image = Vulkan::ImageHandle();
}
void rdp_close() {
messages = std::queue<Message>();
achievement_challenge_indicator_image = Vulkan::ImageHandle();
achievement_progress_indicator_image = Vulkan::ImageHandle();
leaderboard_tracker_image = Vulkan::ImageHandle();
if (wsi)
wsi->end_frame();
@ -490,6 +496,8 @@ static void render_frame(Vulkan::Device &device) {
}
} else if (achievement_progress_indicator_image) {
draw_indicator(cmd, achievement_progress_indicator_image, vp);
} else if (leaderboard_tracker_image) {
draw_indicator(cmd, leaderboard_tracker_image, vp);
} else if (achievement_challenge_indicator_image) {
draw_indicator(cmd, achievement_challenge_indicator_image, vp);
}
@ -794,8 +802,9 @@ uint64_t rdp_process_commands() {
static void update_challenge_indicator() {
std::string message;
for (const auto &achievement_title : achievement_challenge_indicators) {
message += achievement_title;
const auto &v = achievement_challenge_indicators;
for (size_t i = 0; i < std::min<size_t>(v.size(), 5); ++i) {
message += v[i];
message += '\n';
}
achievement_challenge_indicator_image = create_message_image(
@ -803,6 +812,16 @@ static void update_challenge_indicator() {
message.c_str());
}
static void update_leaderboard_tracker() {
std::string message;
for (const auto &leaderboard_tracker : leaderboard_trackers) {
message += leaderboard_tracker.second;
message += '\n';
}
leaderboard_tracker_image =
create_message_image(wsi->get_device(), 0, message_font, message.c_str());
}
void achievement_challenge_indicator_add(const char *achievement_title) {
achievement_challenge_indicators.push_back(achievement_title);
update_challenge_indicator();
@ -828,3 +847,18 @@ void achievement_progress_add(const char *achievement_title,
void achievement_progress_remove(const char *achievement_title) {
achievement_progress_indicator_image = Vulkan::ImageHandle();
}
void leaderboard_tracker_add(uint32_t id, const char *display) {
std::string message = display;
leaderboard_trackers[id] = message;
update_leaderboard_tracker();
}
void leaderboard_tracker_remove(uint32_t id) {
leaderboard_trackers.erase(id);
if (leaderboard_trackers.empty()) {
leaderboard_tracker_image = Vulkan::ImageHandle();
} else {
update_leaderboard_tracker();
}
}

View File

@ -56,6 +56,8 @@ void achievement_challenge_indicator_remove(const char *achievement_title);
void achievement_progress_add(const char *achievement_title,
const char *progress);
void achievement_progress_remove(const char *achievement_title);
void leaderboard_tracker_add(uint32_t id, const char *display);
void leaderboard_tracker_remove(uint32_t id);
#ifdef __cplusplus
}

View File

@ -20,6 +20,7 @@ static size_t g_dmem_size = 0;
static bool g_game_loaded = false;
static bool g_user_logged_in = false;
static bool g_challenge = false;
static bool g_leaderboard = false;
static const char *g_username = NULL;
static const char *g_token = NULL;
static char load_game_error_message[512];
@ -181,7 +182,7 @@ void ra_set_dmem(const uint8_t *dmem, size_t dmem_size) {
static void leaderboard_started(const rc_client_leaderboard_t *leaderboard) {
char buffer[512];
snprintf(buffer, sizeof(buffer), "Lboard attempt started: %s",
snprintf(buffer, sizeof(buffer), "Leaderboard started: %s",
leaderboard->title);
rdp_onscreen_message(buffer);
}
@ -189,7 +190,7 @@ static void leaderboard_started(const rc_client_leaderboard_t *leaderboard) {
static void leaderboard_failed(const rc_client_leaderboard_t *leaderboard) {
char buffer[512];
snprintf(buffer, sizeof(buffer), "Lboard attempt failed: %s",
snprintf(buffer, sizeof(buffer), "Leaderboard failed: %s",
leaderboard->title);
rdp_onscreen_message(buffer);
}
@ -197,7 +198,7 @@ static void leaderboard_failed(const rc_client_leaderboard_t *leaderboard) {
static void leaderboard_submitted(const rc_client_leaderboard_t *leaderboard) {
char buffer[512];
snprintf(buffer, sizeof(buffer), "Lboard submitted: %s - %s",
snprintf(buffer, sizeof(buffer), "Leaderboard submitted: %s - %s",
leaderboard->title, leaderboard->tracker_value);
rdp_onscreen_message(buffer);
}
@ -243,13 +244,16 @@ static void event_handler(const rc_client_event_t *event, rc_client_t *client) {
achievement_triggered(event->achievement);
break;
case RC_CLIENT_EVENT_LEADERBOARD_STARTED:
leaderboard_started(event->leaderboard);
if (g_leaderboard)
leaderboard_started(event->leaderboard);
break;
case RC_CLIENT_EVENT_LEADERBOARD_FAILED:
leaderboard_failed(event->leaderboard);
if (g_leaderboard)
leaderboard_failed(event->leaderboard);
break;
case RC_CLIENT_EVENT_LEADERBOARD_SUBMITTED:
leaderboard_submitted(event->leaderboard);
if (g_leaderboard)
leaderboard_submitted(event->leaderboard);
break;
case RC_CLIENT_EVENT_ACHIEVEMENT_CHALLENGE_INDICATOR_SHOW:
if (g_challenge)
@ -271,10 +275,18 @@ static void event_handler(const rc_client_event_t *event, rc_client_t *client) {
event->achievement->measured_progress);
break;
case RC_CLIENT_EVENT_LEADERBOARD_TRACKER_SHOW:
if (g_leaderboard)
leaderboard_tracker_add(event->leaderboard_tracker->id,
event->leaderboard_tracker->display);
break;
case RC_CLIENT_EVENT_LEADERBOARD_TRACKER_HIDE:
if (g_leaderboard)
leaderboard_tracker_remove(event->leaderboard_tracker->id);
break;
case RC_CLIENT_EVENT_LEADERBOARD_TRACKER_UPDATE:
if (g_leaderboard)
leaderboard_tracker_add(event->leaderboard_tracker->id,
event->leaderboard_tracker->display);
break;
case RC_CLIENT_EVENT_LEADERBOARD_SCOREBOARD:
break;
@ -293,7 +305,7 @@ static void event_handler(const rc_client_event_t *event, rc_client_t *client) {
}
}
void ra_init_client(bool hardcore, bool challenge) {
void ra_init_client(bool hardcore, bool challenge, bool leaderboard) {
// Create the client instance (using a global variable simplifies this
// example)
g_client = rc_client_create(read_memory, server_call);
@ -311,6 +323,7 @@ void ra_init_client(bool hardcore, bool challenge) {
rc_client_set_hardcore_enabled(g_client, hardcore);
g_challenge = challenge;
g_leaderboard = leaderboard;
}
bool ra_get_hardcore() {

View File

@ -2,7 +2,7 @@
#include <stddef.h>
#include <stdint.h>
void ra_init_client(bool hardcore, bool challenge);
void ra_init_client(bool hardcore, bool challenge, bool leaderboard);
void ra_shutdown_client();
void ra_welcome();
bool ra_get_hardcore();

View File

@ -128,10 +128,10 @@ pub fn vertical_interrupt_event(device: &mut device::Device) {
cheats::execute_cheats(device, device.cheats.cheats.clone());
}
retroachievements::do_frame();
ui::video::render_frame();
retroachievements::do_frame();
let (mut speed_limiter_toggled, paused) = ui::video::check_callback(device);
if let Some(netplay) = &mut device.netplay {

View File

@ -111,6 +111,11 @@ struct Args {
help = "Enable Challenge Indicators for RetroAchievements"
)]
ra_challenge: bool,
#[arg(
long = "ra-leaderboard",
help = "Enable Leaderboard Trackers for RetroAchievements"
)]
ra_leaderboard: bool,
}
#[tokio::main(worker_threads = 4)]
@ -164,9 +169,13 @@ async fn main() -> std::io::Result<()> {
};
if cfg!(ra_hardcore_enabled) {
retroachievements::init_client(args.ra_hardcore, args.ra_challenge);
retroachievements::init_client(
args.ra_hardcore,
args.ra_challenge,
args.ra_leaderboard,
);
} else {
retroachievements::init_client(false, args.ra_challenge);
retroachievements::init_client(false, args.ra_challenge, args.ra_leaderboard);
}
let mut shutdown_tx = None;
@ -264,7 +273,7 @@ async fn main() -> std::io::Result<()> {
ui::input::bind_input_profile(&mut ui, profile, port);
}
} else {
retroachievements::init_client(false, false);
retroachievements::init_client(false, false, false);
gui::app_window();
retroachievements::shutdown_client();
}

View File

@ -10,6 +10,7 @@ pub struct RAConfig {
pub enabled: bool,
pub hardcore: bool,
pub challenge: bool,
pub leaderboard: bool,
}
#[unsafe(no_mangle)]
@ -41,6 +42,7 @@ pub extern "C" fn store_retroachievements_credentials(
enabled: result.enabled,
hardcore: result.hardcore,
challenge: result.challenge,
leaderboard: result.leaderboard,
}
} else {
RAConfig {
@ -49,6 +51,7 @@ pub extern "C" fn store_retroachievements_credentials(
enabled: false,
hardcore: false,
challenge: false,
leaderboard: false,
}
};
let f = std::fs::File::create(&file_path).unwrap();
@ -170,6 +173,7 @@ pub fn ra_window(app: &ui::gui::AppWindow) {
app.set_ra_enabled(result.enabled);
app.set_ra_hardcore(result.hardcore);
app.set_ra_challenge(result.challenge);
app.set_ra_leaderboard(result.leaderboard);
token = result.token;
} else {
app.set_ra_hardcore(true);
@ -199,7 +203,7 @@ pub fn ra_window(app: &ui::gui::AppWindow) {
.unwrap();
});
app.on_ra_toggled(move |enabled, hardcore, challenge| {
app.on_ra_toggled(move |enabled, hardcore, challenge, leaderboard| {
let file_path = ui::get_dirs().config_dir.join("retroachievements.json");
let raconfig = if let Ok(ra_config) = std::fs::read(&file_path)
&& let Ok(result) = serde_json::from_slice::<RAConfig>(ra_config.as_ref())
@ -212,6 +216,7 @@ pub fn ra_window(app: &ui::gui::AppWindow) {
enabled,
hardcore,
challenge,
leaderboard,
}
} else {
RAConfig {
@ -220,6 +225,7 @@ pub fn ra_window(app: &ui::gui::AppWindow) {
enabled,
hardcore,
challenge,
leaderboard,
}
}
} else {
@ -229,6 +235,7 @@ pub fn ra_window(app: &ui::gui::AppWindow) {
enabled,
hardcore,
challenge,
leaderboard,
}
};
let f = std::fs::File::create(&file_path).unwrap();
@ -269,8 +276,8 @@ pub fn do_idle() {
unsafe { ra_do_idle() };
}
pub fn init_client(hardcore: bool, challenge: bool) {
unsafe { ra_init_client(hardcore, challenge) };
pub fn init_client(hardcore: bool, challenge: bool, leaderboard: bool) {
unsafe { ra_init_client(hardcore, challenge, leaderboard) };
}
pub fn shutdown_client() {

View File

@ -34,6 +34,7 @@ pub struct GameSettings {
pub struct RASettings {
pub hardcore: bool,
pub challenge: bool,
pub leaderboard: bool,
}
fn check_latest_version(weak: slint::Weak<AppWindow>) {
@ -370,6 +371,9 @@ pub fn run_rom(
if ra_settings.challenge {
command.args(["--ra-challenge"]);
}
if ra_settings.leaderboard {
command.args(["--ra-leaderboard"]);
}
}
for i in 0..4 {
@ -434,6 +438,7 @@ fn open_rom(app: &AppWindow) {
let disable_expansion_pak = app.get_disable_expansion_pak();
let ra_hardcore = app.get_ra_hardcore();
let ra_challenge = app.get_ra_challenge();
let ra_leaderboard = app.get_ra_leaderboard();
let weak = app.as_weak();
tokio::spawn(async move {
@ -467,6 +472,7 @@ fn open_rom(app: &AppWindow) {
RASettings {
hardcore: ra_hardcore,
challenge: ra_challenge,
leaderboard: ra_leaderboard,
},
weak,
);

View File

@ -38,7 +38,7 @@ export component AppWindow inherits Window {
callback ra_button_clicked(string);
callback ra_games_clicked;
callback ra_show_profile_clicked(string);
callback ra_toggled(bool,bool,bool);
callback ra_toggled(bool,bool,bool,bool);
in property version <=> AboutData.version;
in property has_update <=> AboutData.has_update;
in-out property ra_username <=> RAData.ra_username;
@ -49,6 +49,7 @@ export component AppWindow inherits Window {
in-out property ra_enabled <=> RAData.enabled;
in-out property ra_hardcore <=> RAData.hardcore;
in-out property ra_challenge <=> RAData.challenge;
in-out property ra_leaderboard <=> RAData.leaderboard;
in-out property integer_scaling <=> SettingsData.integer_scaling;
in-out property fullscreen <=> SettingsData.fullscreen;
in-out property widescreen <=> SettingsData.widescreen;
@ -135,8 +136,8 @@ export component AppWindow inherits Window {
ra_button_clicked(password) => {
ra_button_clicked(password);
}
ra_toggled(enabled,hardcore,challenge) => {
ra_toggled(enabled,hardcore,challenge);
ra_toggled(enabled,hardcore,challenge,leaderboard) => {
ra_toggled(enabled,hardcore,challenge,leaderboard);
}
ra_games_clicked => {
ra_games_clicked();

View File

@ -8,6 +8,7 @@ export global RAData {
in-out property <bool> enabled;
in-out property <bool> hardcore;
in-out property <bool> challenge;
in-out property <bool> leaderboard;
in-out property <bool> ra_logging_in;
in-out property <bool> ra_show_profile;
in-out property <string> ra_username;
@ -17,7 +18,7 @@ export global RAData {
export component RetroAchievements inherits Page {
callback ra_button_clicked(string);
callback ra_toggled(bool,bool,bool);
callback ra_toggled(bool,bool,bool,bool);
callback ra_games_clicked;
callback ra_show_profile_clicked(string);
title: @tr("RetroAchievements");
@ -38,7 +39,7 @@ export component RetroAchievements inherits Page {
RAData.current_user_message = "Not currently logged in";
RAData.ra_show_profile = false;
}
ra_toggled(RAData.enabled,RAData.hardcore,RAData.challenge);
ra_toggled(RAData.enabled,RAData.hardcore,RAData.challenge,RAData.leaderboard);
}
}
}
@ -50,7 +51,7 @@ export component RetroAchievements inherits Page {
enabled: RAData.enabled;
toggled => {
RAData.hardcore = self.checked;
ra_toggled(RAData.enabled,RAData.hardcore,RAData.challenge);
ra_toggled(RAData.enabled,RAData.hardcore,RAData.challenge,RAData.leaderboard);
}
}
}
@ -62,7 +63,19 @@ export component RetroAchievements inherits Page {
enabled: RAData.enabled;
toggled => {
RAData.challenge = self.checked;
ra_toggled(RAData.enabled,RAData.hardcore,RAData.challenge);
ra_toggled(RAData.enabled,RAData.hardcore,RAData.challenge,RAData.leaderboard);
}
}
}
HorizontalBox {
alignment: center;
CheckBox {
text: @tr("Leaderboard Trackers");
checked: RAData.leaderboard;
enabled: RAData.enabled;
toggled => {
RAData.leaderboard = self.checked;
ra_toggled(RAData.enabled,RAData.hardcore,RAData.challenge,RAData.leaderboard);
}
}
}

View File

@ -1110,6 +1110,7 @@ fn setup_wait_window(
RASettings {
hardcore: false,
challenge: false,
leaderboard: false,
},
weak_app,
);