@@ -905,11 +905,22 @@ int rc_client_user_get_image_url(const rc_client_user_t* user, char buffer[], si
905905 return rc_client_get_image_url (buffer , buffer_size , RC_IMAGE_TYPE_USER , user -> display_name );
906906}
907907
908- static void rc_client_subset_get_user_game_summary (const rc_client_subset_info_t * subset ,
909- rc_client_user_game_summary_t * summary , const uint8_t unlock_bit )
908+ static void rc_client_subset_get_user_game_summary (const rc_client_t * client ,
909+ const rc_client_subset_info_t * subset , rc_client_user_game_summary_t * summary )
910910{
911911 rc_client_achievement_info_t * achievement = subset -> achievements ;
912912 rc_client_achievement_info_t * stop = achievement + subset -> public_ .num_achievements ;
913+ time_t last_unlock_time = 0 ;
914+ time_t last_progression_time = 0 ;
915+ time_t first_win_time = 0 ;
916+ int num_progression_achievements = 0 ;
917+ int num_win_achievements = 0 ;
918+ int num_unlocked_progression_achievements = 0 ;
919+ const uint8_t unlock_bit = (client -> state .hardcore ) ?
920+ RC_CLIENT_ACHIEVEMENT_UNLOCKED_HARDCORE : RC_CLIENT_ACHIEVEMENT_UNLOCKED_SOFTCORE ;
921+
922+ rc_mutex_lock ((rc_mutex_t * )& client -> state .mutex ); /* remove const cast for mutex access */
923+
913924 for (; achievement < stop ; ++ achievement ) {
914925 switch (achievement -> public_ .category ) {
915926 case RC_CLIENT_ACHIEVEMENT_CATEGORY_CORE :
@@ -919,10 +930,28 @@ static void rc_client_subset_get_user_game_summary(const rc_client_subset_info_t
919930 if (achievement -> public_ .unlocked & unlock_bit ) {
920931 ++ summary -> num_unlocked_achievements ;
921932 summary -> points_unlocked += achievement -> public_ .points ;
933+
934+ if (achievement -> public_ .unlock_time > last_unlock_time )
935+ last_unlock_time = achievement -> public_ .unlock_time ;
936+
937+ if (achievement -> public_ .type == RC_CLIENT_ACHIEVEMENT_TYPE_PROGRESSION ) {
938+ ++ num_unlocked_progression_achievements ;
939+ if (achievement -> public_ .unlock_time > last_progression_time )
940+ last_progression_time = achievement -> public_ .unlock_time ;
941+ }
942+ else if (achievement -> public_ .type == RC_CLIENT_ACHIEVEMENT_TYPE_WIN ) {
943+ if (first_win_time == 0 || achievement -> public_ .unlock_time < first_win_time )
944+ first_win_time = achievement -> public_ .unlock_time ;
945+ }
922946 }
923- if (achievement -> public_ .bucket == RC_CLIENT_ACHIEVEMENT_BUCKET_UNSUPPORTED ) {
947+
948+ if (achievement -> public_ .type == RC_CLIENT_ACHIEVEMENT_TYPE_PROGRESSION )
949+ ++ num_progression_achievements ;
950+ else if (achievement -> public_ .type == RC_CLIENT_ACHIEVEMENT_TYPE_WIN )
951+ ++ num_win_achievements ;
952+
953+ if (achievement -> public_ .bucket == RC_CLIENT_ACHIEVEMENT_BUCKET_UNSUPPORTED )
924954 ++ summary -> num_unsupported_achievements ;
925- }
926955
927956 break ;
928957
@@ -934,13 +963,18 @@ static void rc_client_subset_get_user_game_summary(const rc_client_subset_info_t
934963 continue ;
935964 }
936965 }
966+
967+ rc_mutex_unlock ((rc_mutex_t * )& client -> state .mutex ); /* remove const cast for mutex access */
968+
969+ if (summary -> num_unlocked_achievements == summary -> num_core_achievements )
970+ summary -> completed_time = last_unlock_time ;
971+
972+ if ((first_win_time || num_win_achievements == 0 ) && num_unlocked_progression_achievements == num_progression_achievements )
973+ summary -> beaten_time = (first_win_time > last_progression_time ) ? first_win_time : last_progression_time ;
937974}
938975
939976void rc_client_get_user_game_summary (const rc_client_t * client , rc_client_user_game_summary_t * summary )
940977{
941- const uint8_t unlock_bit = (client -> state .hardcore ) ?
942- RC_CLIENT_ACHIEVEMENT_UNLOCKED_HARDCORE : RC_CLIENT_ACHIEVEMENT_UNLOCKED_SOFTCORE ;
943-
944978 if (!summary )
945979 return ;
946980
@@ -949,20 +983,47 @@ void rc_client_get_user_game_summary(const rc_client_t* client, rc_client_user_g
949983 return ;
950984
951985#ifdef RC_CLIENT_SUPPORTS_EXTERNAL
952- if (client -> state .external_client && client -> state .external_client -> get_user_game_summary ) {
953- client -> state .external_client -> get_user_game_summary (summary );
954- return ;
986+ if (client -> state .external_client ) {
987+ if (client -> state .external_client -> get_user_game_summary_v5 ) {
988+ client -> state .external_client -> get_user_game_summary_v5 (summary );
989+ return ;
990+ }
991+ if (client -> state .external_client -> get_user_game_summary ) {
992+ client -> state .external_client -> get_user_game_summary (summary );
993+ return ;
994+ }
955995 }
956996#endif
957997
958- if (!rc_client_is_game_loaded (client ))
998+ if (rc_client_is_game_loaded (client ))
999+ rc_client_subset_get_user_game_summary (client , client -> game -> subsets , summary );
1000+ }
1001+
1002+ void rc_client_get_user_subset_summary (const rc_client_t * client , uint32_t subset_id , rc_client_user_game_summary_t * summary )
1003+ {
1004+ if (!summary )
9591005 return ;
9601006
961- rc_mutex_lock ((rc_mutex_t * )& client -> state .mutex ); /* remove const cast for mutex access */
1007+ memset (summary , 0 , sizeof (* summary ));
1008+ if (!client || !subset_id )
1009+ return ;
9621010
963- rc_client_subset_get_user_game_summary (client -> game -> subsets , summary , unlock_bit );
1011+ #ifdef RC_CLIENT_SUPPORTS_EXTERNAL
1012+ if (client -> state .external_client && client -> state .external_client -> get_user_subset_summary ) {
1013+ client -> state .external_client -> get_user_subset_summary (subset_id , summary );
1014+ return ;
1015+ }
1016+ #endif
9641017
965- rc_mutex_unlock ((rc_mutex_t * )& client -> state .mutex ); /* remove const cast for mutex access */
1018+ if (rc_client_is_game_loaded (client )) {
1019+ const rc_client_subset_info_t * subset = client -> game -> subsets ;
1020+ for (; subset ; subset = subset -> next ) {
1021+ if (subset -> public_ .id == subset_id ) {
1022+ rc_client_subset_get_user_game_summary (client , subset , summary );
1023+ break ;
1024+ }
1025+ }
1026+ }
9661027}
9671028
9681029typedef struct rc_client_fetch_all_user_progress_callback_data_t {
@@ -1376,29 +1437,34 @@ static uint32_t rc_client_subset_toggle_hardcore_achievements(rc_client_subset_i
13761437 break ;
13771438 }
13781439 }
1379- else if (achievement -> public_ .state == RC_CLIENT_ACHIEVEMENT_STATE_ACTIVE ||
1380- achievement -> public_ .state == RC_CLIENT_ACHIEVEMENT_STATE_INACTIVE ) {
1440+ else {
1441+ achievement -> public_ .unlock_time = (active_bit == RC_CLIENT_ACHIEVEMENT_UNLOCKED_HARDCORE ) ?
1442+ achievement -> unlock_time_hardcore : achievement -> unlock_time_softcore ;
13811443
1382- /* if it's active despite being unlocked, and we're in encore mode, leave it active */
1383- if (client -> state .encore_mode ) {
1384- ++ active_count ;
1385- continue ;
1386- }
1444+ if (achievement -> public_ .state == RC_CLIENT_ACHIEVEMENT_STATE_ACTIVE ||
1445+ achievement -> public_ .state == RC_CLIENT_ACHIEVEMENT_STATE_INACTIVE ) {
1446+ /* if it's active despite being unlocked, and we're in encore mode, leave it active */
1447+ if (client -> state .encore_mode ) {
1448+ ++ active_count ;
1449+ continue ;
1450+ }
13871451
1388- achievement -> public_ .state = RC_CLIENT_ACHIEVEMENT_STATE_UNLOCKED ;
1389- achievement -> public_ .unlock_time = (active_bit == RC_CLIENT_ACHIEVEMENT_UNLOCKED_HARDCORE ) ?
1390- achievement -> unlock_time_hardcore : achievement -> unlock_time_softcore ;
1452+ /* switch to inactive */
1453+ achievement -> public_ .state = RC_CLIENT_ACHIEVEMENT_STATE_UNLOCKED ;
1454+
1455+ if (achievement -> trigger && rc_trigger_state_active (achievement -> trigger -> state )) {
1456+ /* hide any active challenge indicators */
1457+ if (achievement -> trigger -> state == RC_TRIGGER_STATE_PRIMED ) {
1458+ rc_client_event_t client_event ;
1459+ memset (& client_event , 0 , sizeof (client_event ));
1460+ client_event .type = RC_CLIENT_EVENT_ACHIEVEMENT_CHALLENGE_INDICATOR_HIDE ;
1461+ client_event .achievement = & achievement -> public_ ;
1462+ client -> callbacks .event_handler (& client_event , client );
1463+ }
13911464
1392- if (achievement -> trigger && achievement -> trigger -> state == RC_TRIGGER_STATE_PRIMED ) {
1393- rc_client_event_t client_event ;
1394- memset (& client_event , 0 , sizeof (client_event ));
1395- client_event .type = RC_CLIENT_EVENT_ACHIEVEMENT_CHALLENGE_INDICATOR_HIDE ;
1396- client_event .achievement = & achievement -> public_ ;
1397- client -> callbacks .event_handler (& client_event , client );
1465+ achievement -> trigger -> state = RC_TRIGGER_STATE_TRIGGERED ;
1466+ }
13981467 }
1399-
1400- if (achievement -> trigger && rc_trigger_state_active (achievement -> trigger -> state ))
1401- achievement -> trigger -> state = RC_TRIGGER_STATE_TRIGGERED ;
14021468 }
14031469 }
14041470
@@ -1728,7 +1794,9 @@ static void rc_client_activate_game(rc_client_load_state_t* load_state, rc_api_s
17281794 if (load_state -> hash -> hash [0 ] != '[' ) {
17291795 if (load_state -> client -> state .spectator_mode != RC_CLIENT_SPECTATOR_MODE_LOCKED ) {
17301796 /* schedule the periodic ping */
1731- rc_client_scheduled_callback_data_t * callback_data = (rc_client_scheduled_callback_data_t * )rc_buffer_alloc (& load_state -> game -> buffer , sizeof (rc_client_scheduled_callback_data_t ));
1797+ rc_client_scheduled_callback_data_t * callback_data = (rc_client_scheduled_callback_data_t * )
1798+ rc_buffer_alloc (& load_state -> game -> buffer , sizeof (rc_client_scheduled_callback_data_t ));
1799+
17321800 memset (callback_data , 0 , sizeof (* callback_data ));
17331801 callback_data -> callback = rc_client_ping ;
17341802 callback_data -> related_id = load_state -> game -> public_ .id ;
@@ -1768,7 +1836,9 @@ static void rc_client_dispatch_activate_game(struct rc_client_scheduled_callback
17681836
17691837static void rc_client_queue_activate_game (rc_client_load_state_t * load_state )
17701838{
1771- rc_client_scheduled_callback_data_t * scheduled_callback_data = (rc_client_scheduled_callback_data_t * )calloc (1 , sizeof (rc_client_scheduled_callback_data_t ));
1839+ rc_client_scheduled_callback_data_t * scheduled_callback_data =
1840+ (rc_client_scheduled_callback_data_t * )calloc (1 , sizeof (rc_client_scheduled_callback_data_t ));
1841+
17721842 if (!scheduled_callback_data ) {
17731843 rc_client_load_error (load_state , RC_OUT_OF_MEMORY , rc_error_str (RC_OUT_OF_MEMORY ));
17741844 return ;
@@ -1805,7 +1875,7 @@ static void rc_client_start_session_callback(const rc_api_server_response_t* ser
18051875 outstanding_requests = rc_client_end_load_state (load_state );
18061876
18071877 if (error_message ) {
1808- rc_client_load_error (( rc_client_load_state_t * ) callback_data , result , error_message );
1878+ rc_client_load_error (load_state , result , error_message );
18091879 }
18101880 else if (outstanding_requests < 0 ) {
18111881 /* previous load state was aborted, load_state was free'd */
@@ -1818,7 +1888,7 @@ static void rc_client_start_session_callback(const rc_api_server_response_t* ser
18181888 (rc_api_start_session_response_t * )malloc (sizeof (rc_api_start_session_response_t ));
18191889
18201890 if (!load_state -> start_session_response ) {
1821- rc_client_load_error (( rc_client_load_state_t * ) callback_data , RC_OUT_OF_MEMORY , rc_error_str (RC_OUT_OF_MEMORY ));
1891+ rc_client_load_error (load_state , RC_OUT_OF_MEMORY , rc_error_str (RC_OUT_OF_MEMORY ));
18221892 }
18231893 else {
18241894 /* safer to parse the response again than to try to copy it */
@@ -2796,14 +2866,14 @@ rc_hash_iterator_t* rc_client_get_load_state_hash_iterator(rc_client_t* client)
27962866
27972867static void rc_client_log_hash_message_verbose (const char * message , const rc_hash_iterator_t * iterator )
27982868{
2799- rc_client_load_state_t * load_state = (rc_client_load_state_t * )iterator -> userdata ;
2869+ const rc_client_load_state_t * load_state = (const rc_client_load_state_t * )iterator -> userdata ;
28002870 if (load_state -> client -> state .log_level >= RC_CLIENT_LOG_LEVEL_INFO )
28012871 rc_client_log_message (load_state -> client , message );
28022872}
28032873
28042874static void rc_client_log_hash_message_error (const char * message , const rc_hash_iterator_t * iterator )
28052875{
2806- rc_client_load_state_t * load_state = (rc_client_load_state_t * )iterator -> userdata ;
2876+ const rc_client_load_state_t * load_state = (const rc_client_load_state_t * )iterator -> userdata ;
28072877 if (load_state -> client -> state .log_level >= RC_CLIENT_LOG_LEVEL_ERROR )
28082878 rc_client_log_message (load_state -> client , message );
28092879}
@@ -3113,7 +3183,8 @@ static rc_client_async_handle_t* rc_client_begin_change_media_internal(rc_client
31133183 rc_api_request_t request ;
31143184 int result ;
31153185
3116- if (game_hash -> game_id != RC_CLIENT_UNKNOWN_GAME_ID ) {
3186+ if (game_hash -> game_id != RC_CLIENT_UNKNOWN_GAME_ID || /* previously looked up */
3187+ game_hash -> hash [0 ] == '[' ) { /* internal use - don't try to look up */
31173188 rc_client_change_media_internal (client , game_hash , callback , callback_userdata );
31183189 return NULL ;
31193190 }
@@ -4367,15 +4438,15 @@ rc_client_leaderboard_list_t* rc_client_create_leaderboard_list(rc_client_t* cli
43674438 };
43684439
43694440 if (!client )
4370- return (rc_client_leaderboard_list_t * )calloc (1 , sizeof ( rc_client_leaderboard_list_t ) );
4441+ return (rc_client_leaderboard_list_t * )calloc (1 , list_size );
43714442
43724443#ifdef RC_CLIENT_SUPPORTS_EXTERNAL
43734444 if (client -> state .external_client && client -> state .external_client -> create_leaderboard_list )
43744445 return (rc_client_leaderboard_list_t * )client -> state .external_client -> create_leaderboard_list (grouping );
43754446#endif
43764447
43774448 if (!client -> game )
4378- return (rc_client_leaderboard_list_t * )calloc (1 , sizeof ( rc_client_leaderboard_list_t ) );
4449+ return (rc_client_leaderboard_list_t * )calloc (1 , list_size );
43794450
43804451 memset (& bucket_counts , 0 , sizeof (bucket_counts ));
43814452
@@ -5809,7 +5880,7 @@ void rc_client_do_frame(rc_client_t* client)
58095880
58105881 richpresence = client -> game -> runtime .richpresence ;
58115882 if (richpresence && richpresence -> richpresence )
5812- rc_update_richpresence (richpresence -> richpresence , client -> state .legacy_peek , client , NULL );
5883+ rc_update_richpresence_internal (richpresence -> richpresence , client -> state .legacy_peek , client );
58135884
58145885 rc_mutex_unlock (& client -> state .mutex );
58155886
0 commit comments