From 2404dbda860a935e92a9ec39ed686961508de0d2 Mon Sep 17 00:00:00 2001 From: offtkp Date: Thu, 2 May 2024 23:07:19 +0300 Subject: [PATCH] Challenge indicators and other fixes --- src/main.c | 16 +- src/retro_achievements.cpp | 391 ++++++++++++++++++++++--------------- src/retro_achievements.h | 4 +- 3 files changed, 244 insertions(+), 167 deletions(-) diff --git a/src/main.c b/src/main.c index 0ed035813..55f3bb3a8 100644 --- a/src/main.c +++ b/src/main.c @@ -21,7 +21,6 @@ #ifdef ENABLE_RETRO_ACHIEVEMENTS #include "retro_achievements.h" -#include "rc_consoles.h" #endif #include "gba.h" @@ -6947,12 +6946,21 @@ static void frame(void) { #ifdef ENABLE_RETRO_ACHIEVEMENTS retro_achievements_update_atlases(); - retro_achievements_draw_notifications(screen_x + 30,menu_height + 30); - // TODO: why does screen_width not work correctly - retro_achievements_draw_progress_indicator(screen_x + screen_width - 30,menu_height + 30); + float left = screen_x; + float top = menu_height; + float right = screen_x+screen_width/se_dpi_scale(); + float bottom = height/se_dpi_scale(); + float padding = 30; + + ImDrawList_AddRect(igGetWindowDrawList(),(ImVec2){left,top},(ImVec2){right,bottom},0xff0000ff,0,0,5.0f); + + retro_achievements_draw_notifications(left+padding,top+padding); + retro_achievements_draw_progress_indicator(right-padding,top+padding); retro_achievements_draw_leaderboard_trackers(screen_x + 30, height - 200); + + retro_achievements_draw_challenge_indicators(screen_x + screen_width - 100, height - 200); #endif for(int i=0;i the currently existing atlases, each serving a different image @@ -75,7 +78,7 @@ struct ra_game_state_t; using ra_game_state_ptr = std::shared_ptr; -struct ra_achievement_wrapper_t +struct ra_achievement_t { atlas_tile_t* tile = nullptr; uint32_t id; @@ -83,19 +86,18 @@ struct ra_achievement_wrapper_t std::string description; }; -struct ra_achievement_bucket_wrapper_t +struct ra_bucket_t { uint8_t bucket_id; std::string label; - std::vector> achievements; + std::vector> achievements; }; -struct ra_achievement_list_wrapper_t +struct ra_achievement_list_t { - explicit ra_achievement_list_wrapper_t(ra_game_state_ptr game_state, - rc_client_achievement_list_t* list); + void initialize(ra_game_state_ptr game_state, rc_client_achievement_list_t* list); - std::vector buckets{}; + std::vector buckets{}; }; struct ra_leaderboard_tracker_t @@ -103,7 +105,12 @@ struct ra_leaderboard_tracker_t char display[24] = {0}; }; -struct ra_progress_indicator_wrapper_t +struct ra_challenge_indicator_t +{ + atlas_tile_t* tile = nullptr; +}; + +struct ra_progress_indicator_t { atlas_tile_t* tile = nullptr; std::string title {}; @@ -131,9 +138,10 @@ struct ra_game_state_t atlas_tile_t* game_image; std::vector atlases{}; std::unordered_map image_cache{}; - std::unique_ptr achievement_list; + ra_achievement_list_t achievement_list; std::unordered_map leaderboard_trackers; - ra_progress_indicator_wrapper_t progress_indicator; + std::unordered_map challenges; + ra_progress_indicator_t progress_indicator; std::vector notifications; std::atomic_int outstanding_requests; std::condition_variable cv; @@ -282,9 +290,88 @@ namespace game_state->notifications.push_back(notification); } + ra_achievement_t* retro_achievements_move_bucket(ra_game_state_ptr game_state, uint32_t id, uint8_t bucket_id) + { + // We need to move the achievement from one bucket to another + // Unfortunately we don't know which bucket the achievement is in + // so we need to find it + // This is preferable to reconstructing the entire list + ra_achievement_t* achievement_ptr = nullptr; + for (int i = 0; i < game_state->achievement_list.buckets.size(); i++) + { + ra_bucket_t* bucket = + &game_state->achievement_list.buckets[i]; + for (int j = 0; j < bucket->achievements.size(); j++) + { + if (bucket->achievements[j]->id == id) + { + std::unique_ptr achievement = + std::move(bucket->achievements[j]); + bucket->achievements.erase(bucket->achievements.begin() + j); + achievement_ptr = achievement.get(); + + // Find correct bucket and place the achievement there + bool bucket_found = false; + for (int k = 0; k < game_state->achievement_list.buckets.size(); k++) + { + if (game_state->achievement_list.buckets[k].bucket_id == + bucket_id) + { + game_state->achievement_list.buckets[k].achievements.push_back( + std::move(achievement)); + bucket_found = true; + break; + } + } + + if (!bucket_found) + { + ra_bucket_t bucket; + bucket.bucket_id = bucket_id; + + switch (bucket.bucket_id) + { + case RC_CLIENT_ACHIEVEMENT_BUCKET_LOCKED: + bucket.label = "Locked"; + break; + case RC_CLIENT_ACHIEVEMENT_BUCKET_UNLOCKED: + bucket.label = "Unlocked"; + break; + case RC_CLIENT_ACHIEVEMENT_BUCKET_UNSUPPORTED: + bucket.label = "Unsupported"; + break; + case RC_CLIENT_ACHIEVEMENT_BUCKET_UNOFFICIAL: + bucket.label = "Unofficial"; + break; + case RC_CLIENT_ACHIEVEMENT_BUCKET_RECENTLY_UNLOCKED: + bucket.label = "Recently Unlocked"; + break; + case RC_CLIENT_ACHIEVEMENT_BUCKET_ACTIVE_CHALLENGE: + bucket.label = "Active Challenges"; + break; + case RC_CLIENT_ACHIEVEMENT_BUCKET_ALMOST_THERE: + bucket.label = "Almost There"; + break; + default: + bucket.label = "Unknown"; + break; + } + + bucket.achievements.push_back(std::move(achievement)); + game_state->achievement_list.buckets.emplace( + game_state->achievement_list.buckets.begin(), + std::move(bucket)); + } + break; + } + } + } + + return achievement_ptr; + } + void retro_achievements_achievement_triggered(ra_game_state_ptr game_state, - const rc_client_achievement_t* rc_achievement, - ra_achievement_wrapper_t* achievement) + const rc_client_achievement_t* rc_achievement) { // Need a shared_ptr because otherwise it will go out of scope during download // With a shared_ptr it won't go out of scope because it will be copied in the callback @@ -309,10 +396,17 @@ namespace rc_achievement, RC_CLIENT_ACHIEVEMENT_STATE_UNLOCKED, &url[0], url.size()) == RC_OK) { notification->tile = &game_state->image_cache[url]; - ra_state->download(game_state, url, [game_state, notification, url, achievement]() { - achievement->tile = &game_state->image_cache[url]; - notification->tile = achievement->tile; - game_state->notifications.push_back(*notification); + uint32_t id = rc_achievement->id; + uint8_t bucket = rc_achievement->bucket; + std::unique_lock lock(game_state->mutex); + ra_state->download(game_state, url, [game_state, notification, url, id, bucket]() { + ra_achievement_t* achievement = retro_achievements_move_bucket(game_state, id, bucket); + + if (achievement) { + achievement->tile = &game_state->image_cache[url]; + notification->tile = achievement->tile; + game_state->notifications.push_back(*notification); + } }); } } @@ -327,6 +421,7 @@ namespace url.resize(256); if (rc_client_achievement_get_image_url(rc_achievement, RC_CLIENT_ACHIEVEMENT_STATE_ACTIVE, &url[0], url.size()) == RC_OK) { + std::unique_lock lock(game_state->mutex); ra_state->download(game_state, url, [game_state, url]() { game_state->progress_indicator.tile = &game_state->image_cache[url]; }); @@ -373,8 +468,8 @@ namespace const rc_client_game_t* game = rc_client_get_game_info(ra_state->rc_client); if (rc_client_game_get_image_url(game, &url[0], url.size()) == RC_OK) { + std::unique_lock lock(game_state->mutex); ra_state->download(game_state, url, [game_state, url]() { - // TODO: set game_image? game_state->game_image = &game_state->image_cache[url]; retro_achievements_game_image_loaded(game_state); }); @@ -419,90 +514,8 @@ namespace { case RC_CLIENT_EVENT_ACHIEVEMENT_TRIGGERED: { - printf("[rcheevos]: Achievement unlocked: %s\n", event->achievement->title); - // We need to move the achievement from one bucket to another - // Unfortunately we don't know which bucket the unlocked achievement is in - // so we need to find it ra_game_state_ptr game_state = ra_state->game_state; - ra_achievement_wrapper_t* achievement_ptr = nullptr; - for (int i = 0; i < game_state->achievement_list->buckets.size(); i++) - { - ra_achievement_bucket_wrapper_t* bucket = - &game_state->achievement_list->buckets[i]; - for (int j = 0; j < bucket->achievements.size(); j++) - { - if (bucket->achievements[j]->id == event->achievement->id) - { - std::unique_ptr achievement = - std::move(bucket->achievements[j]); - bucket->achievements.erase(bucket->achievements.begin() + j); - achievement_ptr = achievement.get(); - - // Find correct bucket and place the achievement there - bool bucket_found = false; - for (int k = 0; k < game_state->achievement_list->buckets.size(); k++) - { - if (game_state->achievement_list->buckets[k].bucket_id == - event->achievement->bucket) - { - game_state->achievement_list->buckets[k].achievements.push_back( - std::move(achievement)); - bucket_found = true; - break; - } - } - - if (!bucket_found) - { - ra_achievement_bucket_wrapper_t bucket; - bucket.bucket_id = event->achievement->bucket; - - switch (bucket.bucket_id) - { - case RC_CLIENT_ACHIEVEMENT_BUCKET_LOCKED: - bucket.label = "Locked"; - break; - case RC_CLIENT_ACHIEVEMENT_BUCKET_UNLOCKED: - bucket.label = "Unlocked"; - break; - case RC_CLIENT_ACHIEVEMENT_BUCKET_UNSUPPORTED: - bucket.label = "Unsupported"; - break; - case RC_CLIENT_ACHIEVEMENT_BUCKET_UNOFFICIAL: - bucket.label = "Unofficial"; - break; - case RC_CLIENT_ACHIEVEMENT_BUCKET_RECENTLY_UNLOCKED: - bucket.label = "Recently Unlocked"; - break; - case RC_CLIENT_ACHIEVEMENT_BUCKET_ACTIVE_CHALLENGE: - bucket.label = "Active Challenges"; - break; - case RC_CLIENT_ACHIEVEMENT_BUCKET_ALMOST_THERE: - bucket.label = "Almost There"; - break; - default: - bucket.label = "Unknown"; - break; - } - - bucket.achievements.push_back(std::move(achievement)); - game_state->achievement_list->buckets.emplace( - game_state->achievement_list->buckets.begin(), - std::move(bucket)); - } - break; - } - } - } - - if (!achievement_ptr) - { - printf("[rcheevos] Error: couldn't find triggered achievement in list\n"); - exit(1); - } - - retro_achievements_achievement_triggered(ra_state->game_state, event->achievement, - achievement_ptr); + retro_achievements_achievement_triggered(game_state, event->achievement); break; } case RC_CLIENT_EVENT_LEADERBOARD_STARTED: { @@ -541,7 +554,7 @@ namespace case RC_CLIENT_EVENT_LEADERBOARD_TRACKER_UPDATE: case RC_CLIENT_EVENT_LEADERBOARD_TRACKER_SHOW: { ra_game_state_ptr game_state = ra_state->game_state; - printf("ID: %d, Display: %s\n", event->leaderboard_tracker->id, event->leaderboard_tracker->display); + std::unique_lock lock(game_state->mutex); ra_leaderboard_tracker_t tracker; memcpy(tracker.display, event->leaderboard_tracker->display, sizeof(tracker.display)); @@ -559,14 +572,36 @@ namespace game_state->leaderboard_trackers.erase(event->leaderboard_tracker->id); break; } - case RC_CLIENT_EVENT_ACHIEVEMENT_CHALLENGE_INDICATOR_SHOW: - printf("STUB: Challenge indicator show\n"); - // se_ra_challenge_indicator_show(event->achievement); + case RC_CLIENT_EVENT_ACHIEVEMENT_CHALLENGE_INDICATOR_SHOW: { + ra_game_state_ptr game_state = ra_state->game_state; + + std::unique_lock lock(game_state->mutex); + uint32_t id = event->achievement->id; + retro_achievements_move_bucket(game_state, event->achievement->id, event->achievement->bucket); + ra_challenge_indicator_t indicator; + game_state->challenges[id] = indicator; + lock.unlock(); + + std::string url; + url.resize(256); + if (rc_client_achievement_get_image_url( + event->achievement, RC_CLIENT_ACHIEVEMENT_STATE_UNLOCKED, &url[0], url.size()) == RC_OK) + { + std::unique_lock lock(game_state->mutex); + ra_state->download(game_state, url, [game_state, url, id]() { + game_state->challenges[id].tile = &game_state->image_cache[url]; + }); + } break; - case RC_CLIENT_EVENT_ACHIEVEMENT_CHALLENGE_INDICATOR_HIDE: - printf("STUB: Challenge indicator hide\n"); - // se_ra_challenge_indicator_hide(event->achievement); + } + case RC_CLIENT_EVENT_ACHIEVEMENT_CHALLENGE_INDICATOR_HIDE: { + ra_game_state_ptr game_state = ra_state->game_state; + + std::unique_lock lock(game_state->mutex); + retro_achievements_move_bucket(game_state, event->achievement->id, RC_CLIENT_ACHIEVEMENT_BUCKET_LOCKED); + game_state->challenges.erase(event->achievement->id); break; + } case RC_CLIENT_EVENT_ACHIEVEMENT_PROGRESS_INDICATOR_SHOW: { ra_game_state_ptr game_state = ra_state->game_state; game_state->progress_indicator.show = true; @@ -668,39 +703,39 @@ namespace return; std::unique_lock lock(ra_state->game_state->mutex); - if (ra_state->game_state->achievement_list) + for (int i = 0; i < ra_state->game_state->achievement_list.buckets.size(); i++) { - for (int i = 0; i < ra_state->game_state->achievement_list->buckets.size(); i++) + ra_bucket_t* bucket = + &ra_state->game_state->achievement_list.buckets[i]; + std::string label = category_to_icon(bucket->bucket_id) + " " + se_localize_and_cache(bucket->label.c_str()); + se_text(label.c_str()); + for (int j = 0; j < bucket->achievements.size(); j++) { - ra_achievement_bucket_wrapper_t* bucket = - &ra_state->game_state->achievement_list->buckets[i]; - std::string label = category_to_icon(bucket->bucket_id) + " " + se_localize_and_cache(bucket->label.c_str()); - se_text(label.c_str()); - for (int j = 0; j < bucket->achievements.size(); j++) + sg_image image = {SG_INVALID_ID}; + ImVec2 uv0, uv1; + if (bucket->achievements[j]->tile) { - sg_image image = {SG_INVALID_ID}; - ImVec2 uv0, uv1; - if (bucket->achievements[j]->tile) - { - atlas_tile_t* tile = bucket->achievements[j]->tile; - uv0 = ImVec2{tile->x1, tile->y1}; - uv1 = ImVec2{tile->x2, tile->y2}; - image.id = tile->atlas_id; - } - - const auto& achievement = bucket->achievements[j]; - se_boxed_image_dual_label(achievement->title.c_str(), - achievement->description.c_str(), ICON_FK_SPINNER, - image, 0, uv0, uv1); + atlas_tile_t* tile = bucket->achievements[j]->tile; + uv0 = ImVec2{tile->x1, tile->y1}; + uv1 = ImVec2{tile->x2, tile->y2}; + image.id = tile->atlas_id; } + + const auto& achievement = bucket->achievements[j]; + se_boxed_image_dual_label(achievement->title.c_str(), + achievement->description.c_str(), ICON_FK_SPINNER, + image, 0, uv0, uv1); } } } } // namespace -ra_achievement_list_wrapper_t::ra_achievement_list_wrapper_t(ra_game_state_ptr game_state, - rc_client_achievement_list_t* list) +void ra_achievement_list_t::initialize(ra_game_state_ptr game_state, rc_client_achievement_list_t* list) { + std::unique_lock lock(game_state->mutex); + + buckets.clear(); + buckets.resize(list->num_buckets); for (int i = 0; i < list->num_buckets; i++) @@ -717,7 +752,7 @@ ra_achievement_list_wrapper_t::ra_achievement_list_wrapper_t(ra_game_state_ptr g std::string url; url.resize(512); - buckets[i].achievements[j].reset(new ra_achievement_wrapper_t()); + buckets[i].achievements[j].reset(new ra_achievement_t()); buckets[i].achievements[j]->id = list->buckets[i].achievements[j]->id; buckets[i].achievements[j]->title = list->buckets[i].achievements[j]->title; buckets[i].achievements[j]->description = list->buckets[i].achievements[j]->description; @@ -727,9 +762,7 @@ ra_achievement_list_wrapper_t::ra_achievement_list_wrapper_t(ra_game_state_ptr g url.size()) == RC_OK) { uint32_t id = achievement->id; - // Unsafe. Unfortunately we have to consider achievements unlocking while the game - // is still loading - ra_achievement_wrapper_t* achievement_ptr = buckets[i].achievements[j].get(); + ra_achievement_t* achievement_ptr = buckets[i].achievements[j].get(); ra_state->download(game_state, url, [game_state, url, achievement_ptr]() { atlas_tile_t* tile = &game_state->image_cache[url]; achievement_ptr->tile = tile; @@ -754,7 +787,6 @@ ra_achievement_list_wrapper_t::ra_achievement_list_wrapper_t(ra_game_state_ptr g void ra_state_t::download(ra_game_state_ptr game_state, const std::string& url, const std::function& callback) { - std::unique_lock glock(game_state->mutex); std::unique_lock lock(global_cache_mutex); if (download_cache.find(url) != download_cache.end()) @@ -901,10 +933,10 @@ void ra_state_t::handle_downloaded(ra_game_state_ptr game_state, const std::stri void ra_state_t::rebuild_achievement_list(ra_game_state_ptr game_state) { - game_state->achievement_list.reset(new ra_achievement_list_wrapper_t( + game_state->achievement_list.initialize( game_state, rc_client_create_achievement_list( rc_client, RC_CLIENT_ACHIEVEMENT_CATEGORY_CORE, // TODO: option for _AND_UNOFFICIAL achievements? - RC_CLIENT_ACHIEVEMENT_LIST_GROUPING_PROGRESS))); + RC_CLIENT_ACHIEVEMENT_LIST_GROUPING_PROGRESS)); } ra_game_state_t::~ra_game_state_t() @@ -927,10 +959,9 @@ void retro_achievements_initialize(void* state) ra_state->rc_client = rc_client_create(retro_achievements_read_memory_callback, retro_achievements_server_callback); -#ifdef DEBUG_RETRO_ACHIEVEMENTS rc_client_enable_logging(ra_state->rc_client, RC_CLIENT_LOG_LEVEL_VERBOSE, retro_achievements_log_callback); -#endif + // TODO: should probably be an option after we're finished testing rc_client_set_hardcore_enabled(ra_state->rc_client, 0); rc_client_set_event_handler(ra_state->rc_client, retro_achievements_event_handler); @@ -1017,8 +1048,7 @@ void retro_achievements_load_game() return; // the old one will be destroyed when the last reference is gone - ra_state->game_state.reset( - new ra_game_state_t()); + ra_state->game_state.reset(new ra_game_state_t()); // We need to create a shared_ptr*, so we can pass it to the C api. ra_game_state_ptr* game_state = new ra_game_state_ptr(ra_state->game_state); @@ -1220,11 +1250,7 @@ void retro_achievements_draw_panel(int win_w) image.id = game_image->atlas_id; offset1 = ImVec2{game_image->x1, game_image->y1}; offset2 = ImVec2{game_image->x2, game_image->y2}; -#ifdef DEBUG_RETRO_ACHIEVEMENTS - snprintf(line2, 256, se_localize_and_cache("Playing: %s (%d)"), game->title, image.id); -#else snprintf(line2, 256, se_localize_and_cache("Playing: %s"), game->title); -#endif } else snprintf(line2, 256, "%s", se_localize_and_cache("No Game Loaded")); @@ -1242,8 +1268,9 @@ void retro_achievements_draw_panel(int win_w) // TODO: make these saveable igCheckbox(se_localize_and_cache("Enable Notifications"), &draw_notifications); - igCheckbox(se_localize_and_cache("Enable Progress Indicator"), &draw_progress_indicator); + igCheckbox(se_localize_and_cache("Enable Progress Indicator"), &draw_progress_indicators); igCheckbox(se_localize_and_cache("Enable Leaderboard Trackers"), &draw_trackers); + igCheckbox(se_localize_and_cache("Enable Challenges"), &draw_challenge_indicators); // TODO: put hardcore mode checkbox here? retro_achievements_draw_achievements(); @@ -1265,7 +1292,6 @@ void retro_achievements_draw_notifications(float left, float top) if (game_state->notifications.empty()) return; - // TODO: confetti? float x = left; float y = top; @@ -1366,7 +1392,7 @@ void retro_achievements_draw_notifications(float left, float top) void retro_achievements_draw_progress_indicator(float right, float top) { - if (!draw_progress_indicator) + if (!draw_progress_indicators) return; ra_game_state_ptr game_state = ra_state->game_state; @@ -1377,7 +1403,7 @@ void retro_achievements_draw_progress_indicator(float right, float top) if (!game_state->progress_indicator.show) return; - ra_progress_indicator_wrapper_t& indicator = game_state->progress_indicator; + ra_progress_indicator_t& indicator = game_state->progress_indicator; ImDrawList_AddCircle(igGetWindowDrawList(), ImVec2{right, top}, 20, 0xff000000, 32, 2.0f); @@ -1401,11 +1427,11 @@ void retro_achievements_draw_progress_indicator(float right, float top) ImDrawList_AddRectFilled(igGetWindowDrawList(), ImVec2{x, y}, ImVec2{x + placard_width, y + placard_height}, - 0xff515151, 8.0f, ImDrawCornerFlags_All); + 0x80515151, 8.0f, ImDrawCornerFlags_All); ImDrawList_AddRect(igGetWindowDrawList(), ImVec2{x + (padding / 2), y + (padding / 2)}, ImVec2{x + placard_width - (padding / 2), y + placard_height - (padding / 2)}, - 0xff000000, 8.0f, ImDrawCornerFlags_All, 2.0f); + 0x80000000, 8.0f, ImDrawCornerFlags_All, 2.0f); if (indicator.tile && indicator.tile->atlas_id != SG_INVALID_ID) { @@ -1415,14 +1441,14 @@ void retro_achievements_draw_progress_indicator(float right, float top) igGetWindowDrawList(), (ImTextureID)(intptr_t)indicator.tile->atlas_id, ImVec2{x + padding, y + padding}, ImVec2{x + padding + image_width, y + padding + image_height}, uv0, uv1, - 0xffffffff, 8.0f, ImDrawCornerFlags_All); + 0x80ffffff, 8.0f, ImDrawCornerFlags_All); } else { ImDrawList_AddRectFilled( igGetWindowDrawList(), ImVec2{x + 15, y + 15}, ImVec2{x + padding + image_width, y + padding + image_height}, - 0xff242424, 8.0f, ImDrawCornerFlags_All); + 0x80242424, 8.0f, ImDrawCornerFlags_All); } ImDrawList_AddTextFontPtr(igGetWindowDrawList(), igGetFont(), 22.0f, @@ -1438,7 +1464,6 @@ void retro_achievements_draw_progress_indicator(float right, float top) void retro_achievements_draw_leaderboard_trackers(float left, float bottom) { - if (!draw_trackers) return; @@ -1459,10 +1484,8 @@ void retro_achievements_draw_leaderboard_trackers(float left, float bottom) ImFont_CalcTextSizeA(&out, font, 12.0f, std::numeric_limits::max(), 0, "A", NULL, NULL); text_height = out.y; - float start_x = left; - float start_y = bottom; - float x = start_x; - float y = start_y; + float x = left; + float y = bottom; int i = 0; igPushFont(font); @@ -1476,7 +1499,6 @@ void retro_achievements_draw_leaderboard_trackers(float left, float bottom) ImVec2{x + max_text_width + padding * 2, y + text_height + padding * 2}, 0x80515151, 0, 0); - // TODO: needs mono font ImDrawList_AddTextFontPtr(igGetWindowDrawList(), igGetFont(), 12.0f, ImVec2{x + padding, y + padding}, 0xffffffff, tracker.second.display, NULL, @@ -1488,7 +1510,7 @@ void retro_achievements_draw_leaderboard_trackers(float left, float bottom) } else { - x = start_x; + x = left; y += text_height + padding * 3; } @@ -1496,4 +1518,51 @@ void retro_achievements_draw_leaderboard_trackers(float left, float bottom) break; // show up to 9 trackers } igPopFont(); +} + +void retro_achievements_draw_challenge_indicators(float right, float bottom) +{ + if (!draw_challenge_indicators) + return; + + ra_game_state_ptr game_state = ra_state->game_state; + + if (!game_state) + return; + + std::unique_lock lock(game_state->mutex); + + if (game_state->challenges.empty()) + return; + + float x = right - 32 * 3 - padding * 2; + float y = bottom; + int i = 0; + + for (const auto& item : game_state->challenges) { + const ra_challenge_indicator_t& challenge = item.second; + + if (challenge.tile && challenge.tile->atlas_id != SG_INVALID_ID) { + ImDrawList_AddImage( + igGetWindowDrawList(), (ImTextureID)(intptr_t)challenge.tile->atlas_id, + ImVec2{x, y}, + ImVec2{x + 32, y + 32}, ImVec2{challenge.tile->x1, challenge.tile->y1}, + ImVec2{challenge.tile->x2, challenge.tile->y2}, + 0x80ffffff); + } + + if (i++ % 3 != 2) + { + x += 32 + padding; + } + else + { + x = right - 32 * 3 - padding * 2; + y += 32 + padding; + } + + if (i == 9) + break; // show up to 9 trackers + } + } \ No newline at end of file diff --git a/src/retro_achievements.h b/src/retro_achievements.h index bd3d6b283..061c747ae 100644 --- a/src/retro_achievements.h +++ b/src/retro_achievements.h @@ -11,8 +11,6 @@ #include "mutex.h" #include -#define DEBUG_RETRO_ACHIEVEMENTS 1 - typedef struct { uint32_t atlas_id; uint32_t width, height; @@ -42,4 +40,6 @@ void retro_achievements_draw_progress_indicator(float right, float top); void retro_achievements_draw_leaderboard_trackers(float left, float bottom); +void retro_achievements_draw_challenge_indicators(float right, float bottom); + #endif \ No newline at end of file