Skip to content

Commit

Permalink
add RC_CLIENT_ACHIEVEMENT_BUCKET_UNSYNCED (#295)
Browse files Browse the repository at this point in the history
  • Loading branch information
Jamiras authored Dec 9, 2023
1 parent bc14dd8 commit af77c83
Show file tree
Hide file tree
Showing 3 changed files with 178 additions and 8 deletions.
3 changes: 2 additions & 1 deletion include/rc_client.h
Original file line number Diff line number Diff line change
Expand Up @@ -312,7 +312,8 @@ enum {
RC_CLIENT_ACHIEVEMENT_BUCKET_RECENTLY_UNLOCKED = 5,
RC_CLIENT_ACHIEVEMENT_BUCKET_ACTIVE_CHALLENGE = 6,
RC_CLIENT_ACHIEVEMENT_BUCKET_ALMOST_THERE = 7,
NUM_RC_CLIENT_ACHIEVEMENT_BUCKETS = 8
RC_CLIENT_ACHIEVEMENT_BUCKET_UNSYNCED = 8,
NUM_RC_CLIENT_ACHIEVEMENT_BUCKETS = 9
};

enum {
Expand Down
40 changes: 33 additions & 7 deletions src/rc_client.c
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ static void rc_client_raise_leaderboard_events(rc_client_t* client, rc_client_su
static void rc_client_raise_pending_events(rc_client_t* client, rc_client_game_info_t* game);
static void rc_client_reschedule_callback(rc_client_t* client, rc_client_scheduled_callback_data_t* callback, rc_clock_t when);
static void rc_client_award_achievement_retry(rc_client_scheduled_callback_data_t* callback_data, rc_client_t* client, rc_clock_t now);
static int rc_client_is_award_achievement_pending(const rc_client_t* client, uint32_t achievement_id);
static void rc_client_submit_leaderboard_entry_retry(rc_client_scheduled_callback_data_t* callback_data, rc_client_t* client, rc_clock_t now);

/* ===== Construction/Destruction ===== */
Expand Down Expand Up @@ -2738,7 +2739,14 @@ static void rc_client_update_achievement_display_information(rc_client_t* client

if (achievement->public_.state == RC_CLIENT_ACHIEVEMENT_STATE_UNLOCKED) {
/* achievement unlocked */
new_bucket = RC_CLIENT_ACHIEVEMENT_BUCKET_UNLOCKED;
if (achievement->public_.unlock_time >= recent_unlock_time) {
new_bucket = RC_CLIENT_ACHIEVEMENT_BUCKET_RECENTLY_UNLOCKED;
} else {
new_bucket = RC_CLIENT_ACHIEVEMENT_BUCKET_UNLOCKED;

if (client->state.disconnect && rc_client_is_award_achievement_pending(client, achievement->public_.id))
new_bucket = RC_CLIENT_ACHIEVEMENT_BUCKET_UNSYNCED;
}
}
else {
/* active achievement */
Expand Down Expand Up @@ -2779,9 +2787,6 @@ static void rc_client_update_achievement_display_information(rc_client_t* client
}
}

if (new_bucket == RC_CLIENT_ACHIEVEMENT_BUCKET_UNLOCKED && achievement->public_.unlock_time >= recent_unlock_time)
new_bucket = RC_CLIENT_ACHIEVEMENT_BUCKET_RECENTLY_UNLOCKED;

achievement->public_.bucket = new_bucket;
}

Expand All @@ -2795,6 +2800,7 @@ static const char* rc_client_get_achievement_bucket_label(uint8_t bucket_type)
case RC_CLIENT_ACHIEVEMENT_BUCKET_RECENTLY_UNLOCKED: return "Recently Unlocked";
case RC_CLIENT_ACHIEVEMENT_BUCKET_ACTIVE_CHALLENGE: return "Active Challenges";
case RC_CLIENT_ACHIEVEMENT_BUCKET_ALMOST_THERE: return "Almost There";
case RC_CLIENT_ACHIEVEMENT_BUCKET_UNSYNCED: return "Unlocks Not Synced to Server";
default: return "Unknown";
}
}
Expand Down Expand Up @@ -2852,6 +2858,7 @@ static uint8_t rc_client_map_bucket(uint8_t bucket, int grouping)
if (grouping == RC_CLIENT_ACHIEVEMENT_LIST_GROUPING_LOCK_STATE) {
switch (bucket) {
case RC_CLIENT_ACHIEVEMENT_BUCKET_RECENTLY_UNLOCKED:
case RC_CLIENT_ACHIEVEMENT_BUCKET_UNSYNCED:
return RC_CLIENT_ACHIEVEMENT_BUCKET_UNLOCKED;

case RC_CLIENT_ACHIEVEMENT_BUCKET_ACTIVE_CHALLENGE:
Expand All @@ -2876,7 +2883,7 @@ rc_client_achievement_list_t* rc_client_create_achievement_list(rc_client_t* cli
rc_client_achievement_list_info_t* list;
rc_client_subset_info_t* subset;
const uint32_t list_size = RC_ALIGN(sizeof(*list));
uint32_t bucket_counts[16];
uint32_t bucket_counts[NUM_RC_CLIENT_ACHIEVEMENT_BUCKETS];
uint32_t num_buckets;
uint32_t num_achievements;
size_t buckets_size;
Expand All @@ -2886,7 +2893,8 @@ rc_client_achievement_list_t* rc_client_create_achievement_list(rc_client_t* cli
const uint8_t shared_bucket_order[] = {
RC_CLIENT_ACHIEVEMENT_BUCKET_ACTIVE_CHALLENGE,
RC_CLIENT_ACHIEVEMENT_BUCKET_RECENTLY_UNLOCKED,
RC_CLIENT_ACHIEVEMENT_BUCKET_ALMOST_THERE
RC_CLIENT_ACHIEVEMENT_BUCKET_ALMOST_THERE,
RC_CLIENT_ACHIEVEMENT_BUCKET_UNSYNCED,
};
const uint8_t subset_bucket_order[] = {
RC_CLIENT_ACHIEVEMENT_BUCKET_LOCKED,
Expand Down Expand Up @@ -3165,6 +3173,24 @@ typedef struct rc_client_award_achievement_callback_data_t
rc_client_scheduled_callback_data_t* scheduled_callback_data;
} rc_client_award_achievement_callback_data_t;

static int rc_client_is_award_achievement_pending(const rc_client_t* client, uint32_t achievement_id)
{
/* assume lock already held */
rc_client_scheduled_callback_data_t* scheduled_callback = client->state.scheduled_callbacks;
for (; scheduled_callback; scheduled_callback = scheduled_callback->next)
{
if (scheduled_callback->callback == rc_client_award_achievement_retry)
{
rc_client_award_achievement_callback_data_t* ach_data =
(rc_client_award_achievement_callback_data_t*)scheduled_callback->data;
if (ach_data->id == achievement_id)
return 1;
}
}

return 0;
}

static void rc_client_award_achievement_server_call(rc_client_award_achievement_callback_data_t* ach_data);

static void rc_client_award_achievement_retry(rc_client_scheduled_callback_data_t* callback_data, rc_client_t* client, rc_clock_t now)
Expand Down Expand Up @@ -5531,7 +5557,7 @@ void rc_client_set_host(const rc_client_t* client, const char* hostname)
rc_api_set_host(hostname);

#ifdef RC_CLIENT_SUPPORTS_EXTERNAL
if (client->state.external_client && client->state.external_client->set_host)
if (client && client->state.external_client && client->state.external_client->set_host)
client->state.external_client->set_host(hostname);
#endif
}
143 changes: 143 additions & 0 deletions test/test_rc_client.c
Original file line number Diff line number Diff line change
Expand Up @@ -3446,6 +3446,7 @@ static void test_achievement_list_buckets(void)
rc_client_destroy_achievement_list(list);
}

/* also check mapping to lock state */
list = rc_client_create_achievement_list(g_client, RC_CLIENT_ACHIEVEMENT_CATEGORY_CORE, RC_CLIENT_ACHIEVEMENT_LIST_GROUPING_LOCK_STATE);
ASSERT_PTR_NOT_NULL(list);
if (list) {
Expand Down Expand Up @@ -3650,6 +3651,147 @@ static void test_achievement_list_buckets_progress_sort_big_ids(void)
rc_client_destroy(g_client);
}

static void test_achievement_list_buckets_with_unsynced(void)
{
rc_client_achievement_list_t* list;
rc_client_achievement_t* achievement;
const char* unlock_request_params = "r=awardachievement&u=Username&t=ApiToken&a=5&h=1&m=0123456789ABCDEF&v=732f8e30e9c1eb08948dda098c305d8b";

uint8_t memory[64];
memset(memory, 0, sizeof(memory));

g_client = mock_client_game_loaded(patchdata_exhaustive, unlock_8);
g_client->callbacks.server_call = rc_client_server_call_async;
mock_memory(memory, sizeof(memory));

/* discard the queued ping to make finding the retry easier */
g_client->state.scheduled_callbacks = NULL;

rc_client_do_frame(g_client); /* advance achievements out of waiting state */
event_count = 0;

memory[5] = 5; /* trigger achievement 5 */
memory[1] = 1; /* begin challenge achievement 7 */
rc_client_do_frame(g_client);
event_count = 0;

/* first failure will immediately requeue the request */
async_api_error(unlock_request_params, response_503, 503);
assert_api_pending(unlock_request_params);
ASSERT_PTR_NULL(g_client->state.scheduled_callbacks);
rc_client_idle(g_client);

/* second failure will queue it */
async_api_error(unlock_request_params, response_503, 503);
assert_api_call_count(unlock_request_params, 0);
ASSERT_PTR_NOT_NULL(g_client->state.scheduled_callbacks);
rc_client_idle(g_client);
event_count = 0;

list = rc_client_create_achievement_list(g_client, RC_CLIENT_ACHIEVEMENT_CATEGORY_CORE, RC_CLIENT_ACHIEVEMENT_LIST_GROUPING_PROGRESS);
ASSERT_PTR_NOT_NULL(list);
if (list)
{
ASSERT_NUM_EQUALS(list->num_buckets, 4);

ASSERT_NUM_EQUALS(list->buckets[0].bucket_type, RC_CLIENT_ACHIEVEMENT_BUCKET_ACTIVE_CHALLENGE);
ASSERT_NUM_EQUALS(list->buckets[0].subset_id, 0);
ASSERT_STR_EQUALS(list->buckets[0].label, "Active Challenges");
ASSERT_NUM_EQUALS(list->buckets[0].num_achievements, 1);
ASSERT_NUM_EQUALS(list->buckets[0].achievements[0]->id, 7);

ASSERT_NUM_EQUALS(list->buckets[1].bucket_type, RC_CLIENT_ACHIEVEMENT_BUCKET_RECENTLY_UNLOCKED);
ASSERT_NUM_EQUALS(list->buckets[1].subset_id, 0);
ASSERT_STR_EQUALS(list->buckets[1].label, "Recently Unlocked");
ASSERT_NUM_EQUALS(list->buckets[1].num_achievements, 1);
ASSERT_NUM_EQUALS(list->buckets[1].achievements[0]->id, 5);

ASSERT_NUM_EQUALS(list->buckets[2].bucket_type, RC_CLIENT_ACHIEVEMENT_BUCKET_LOCKED);
ASSERT_NUM_EQUALS(list->buckets[2].subset_id, 0);
ASSERT_STR_EQUALS(list->buckets[2].label, "Locked");
ASSERT_NUM_EQUALS(list->buckets[2].num_achievements, 4);

ASSERT_NUM_EQUALS(list->buckets[3].bucket_type, RC_CLIENT_ACHIEVEMENT_BUCKET_UNLOCKED);
ASSERT_NUM_EQUALS(list->buckets[3].subset_id, 0);
ASSERT_STR_EQUALS(list->buckets[3].label, "Unlocked");
ASSERT_NUM_EQUALS(list->buckets[3].num_achievements, 1);
ASSERT_NUM_EQUALS(list->buckets[3].achievements[0]->id, 8);

rc_client_destroy_achievement_list(list);
}

/* adjust unlock time on achievement 5 so it's no longer recent */
achievement = (rc_client_achievement_t*)rc_client_get_achievement_info(g_client, 5);
achievement->unlock_time -= 15 * 60;

list = rc_client_create_achievement_list(g_client, RC_CLIENT_ACHIEVEMENT_CATEGORY_CORE, RC_CLIENT_ACHIEVEMENT_LIST_GROUPING_PROGRESS);
ASSERT_PTR_NOT_NULL(list);
if (list)
{
ASSERT_NUM_EQUALS(list->num_buckets, 4);

ASSERT_NUM_EQUALS(list->buckets[0].bucket_type, RC_CLIENT_ACHIEVEMENT_BUCKET_ACTIVE_CHALLENGE);
ASSERT_NUM_EQUALS(list->buckets[0].subset_id, 0);
ASSERT_STR_EQUALS(list->buckets[0].label, "Active Challenges");
ASSERT_NUM_EQUALS(list->buckets[0].num_achievements, 1);
ASSERT_NUM_EQUALS(list->buckets[0].achievements[0]->id, 7);

ASSERT_NUM_EQUALS(list->buckets[1].bucket_type, RC_CLIENT_ACHIEVEMENT_BUCKET_UNSYNCED);
ASSERT_NUM_EQUALS(list->buckets[1].subset_id, 0);
ASSERT_STR_EQUALS(list->buckets[1].label, "Unlocks Not Synced to Server");
ASSERT_NUM_EQUALS(list->buckets[1].num_achievements, 1);
ASSERT_NUM_EQUALS(list->buckets[1].achievements[0]->id, 5);

ASSERT_NUM_EQUALS(list->buckets[2].bucket_type, RC_CLIENT_ACHIEVEMENT_BUCKET_LOCKED);
ASSERT_NUM_EQUALS(list->buckets[2].subset_id, 0);
ASSERT_STR_EQUALS(list->buckets[2].label, "Locked");
ASSERT_NUM_EQUALS(list->buckets[2].num_achievements, 4);

ASSERT_NUM_EQUALS(list->buckets[3].bucket_type, RC_CLIENT_ACHIEVEMENT_BUCKET_UNLOCKED);
ASSERT_NUM_EQUALS(list->buckets[3].subset_id, 0);
ASSERT_STR_EQUALS(list->buckets[3].label, "Unlocked");
ASSERT_NUM_EQUALS(list->buckets[3].num_achievements, 1);
ASSERT_NUM_EQUALS(list->buckets[3].achievements[0]->id, 8);

rc_client_destroy_achievement_list(list);
}

/* allow unlock request to succeed */
g_now += 2 * 1000;
rc_client_idle(g_client);
assert_api_pending(unlock_request_params);
async_api_response(unlock_request_params, "{\"Success\":true,\"Score\":5432,\"SoftcoreScore\":777,\"AchievementID\":8,\"AchievementsRemaining\":11}");

list = rc_client_create_achievement_list(g_client, RC_CLIENT_ACHIEVEMENT_CATEGORY_CORE, RC_CLIENT_ACHIEVEMENT_LIST_GROUPING_PROGRESS);
ASSERT_PTR_NOT_NULL(list);
if (list)
{
ASSERT_NUM_EQUALS(list->num_buckets, 3);

ASSERT_NUM_EQUALS(list->buckets[0].bucket_type, RC_CLIENT_ACHIEVEMENT_BUCKET_ACTIVE_CHALLENGE);
ASSERT_NUM_EQUALS(list->buckets[0].subset_id, 0);
ASSERT_STR_EQUALS(list->buckets[0].label, "Active Challenges");
ASSERT_NUM_EQUALS(list->buckets[0].num_achievements, 1);
ASSERT_NUM_EQUALS(list->buckets[0].achievements[0]->id, 7);

ASSERT_NUM_EQUALS(list->buckets[1].bucket_type, RC_CLIENT_ACHIEVEMENT_BUCKET_LOCKED);
ASSERT_NUM_EQUALS(list->buckets[1].subset_id, 0);
ASSERT_STR_EQUALS(list->buckets[1].label, "Locked");
ASSERT_NUM_EQUALS(list->buckets[1].num_achievements, 4);

ASSERT_NUM_EQUALS(list->buckets[2].bucket_type, RC_CLIENT_ACHIEVEMENT_BUCKET_UNLOCKED);
ASSERT_NUM_EQUALS(list->buckets[2].subset_id, 0);
ASSERT_STR_EQUALS(list->buckets[2].label, "Unlocked");
ASSERT_NUM_EQUALS(list->buckets[2].num_achievements, 2);
ASSERT_NUM_EQUALS(list->buckets[2].achievements[0]->id, 5);
ASSERT_NUM_EQUALS(list->buckets[2].achievements[1]->id, 8);

rc_client_destroy_achievement_list(list);
}

rc_client_destroy(g_client);
}

static void test_achievement_list_subset_with_unofficial_and_unsupported(void)
{
rc_client_achievement_list_t* list;
Expand Down Expand Up @@ -8341,6 +8483,7 @@ void test_client(void) {
TEST(test_achievement_list_buckets);
TEST(test_achievement_list_buckets_progress_sort);
TEST(test_achievement_list_buckets_progress_sort_big_ids);
TEST(test_achievement_list_buckets_with_unsynced);
TEST(test_achievement_list_subset_with_unofficial_and_unsupported);
TEST(test_achievement_list_subset_buckets);
TEST(test_achievement_list_subset_buckets_subset_first);
Expand Down

0 comments on commit af77c83

Please sign in to comment.