dep/rcheevos: Functionality changes

This commit is contained in:
Stenzek 2023-09-12 23:46:42 +10:00
parent 58d62e1ab4
commit f8c5e4982c
2 changed files with 231 additions and 17 deletions

View File

@ -152,7 +152,8 @@ enum
RC_CLIENT_LOG_LEVEL_ERROR = 1, RC_CLIENT_LOG_LEVEL_ERROR = 1,
RC_CLIENT_LOG_LEVEL_WARN = 2, RC_CLIENT_LOG_LEVEL_WARN = 2,
RC_CLIENT_LOG_LEVEL_INFO = 3, RC_CLIENT_LOG_LEVEL_INFO = 3,
RC_CLIENT_LOG_LEVEL_VERBOSE = 4 RC_CLIENT_LOG_LEVEL_VERBOSE = 4,
NUM_RC_CLIENT_LOG_LEVELS = 5
}; };
/*****************************************************************************\ /*****************************************************************************\
@ -286,7 +287,8 @@ enum {
RC_CLIENT_ACHIEVEMENT_STATE_INACTIVE = 0, /* unprocessed */ RC_CLIENT_ACHIEVEMENT_STATE_INACTIVE = 0, /* unprocessed */
RC_CLIENT_ACHIEVEMENT_STATE_ACTIVE = 1, /* eligible to trigger */ RC_CLIENT_ACHIEVEMENT_STATE_ACTIVE = 1, /* eligible to trigger */
RC_CLIENT_ACHIEVEMENT_STATE_UNLOCKED = 2, /* earned by user */ RC_CLIENT_ACHIEVEMENT_STATE_UNLOCKED = 2, /* earned by user */
RC_CLIENT_ACHIEVEMENT_STATE_DISABLED = 3 /* not supported by this version of the runtime */ RC_CLIENT_ACHIEVEMENT_STATE_DISABLED = 3, /* not supported by this version of the runtime */
NUM_RC_CLIENT_ACHIEVEMENT_STATES = 4
}; };
enum { enum {
@ -304,7 +306,8 @@ enum {
RC_CLIENT_ACHIEVEMENT_BUCKET_UNOFFICIAL = 4, RC_CLIENT_ACHIEVEMENT_BUCKET_UNOFFICIAL = 4,
RC_CLIENT_ACHIEVEMENT_BUCKET_RECENTLY_UNLOCKED = 5, RC_CLIENT_ACHIEVEMENT_BUCKET_RECENTLY_UNLOCKED = 5,
RC_CLIENT_ACHIEVEMENT_BUCKET_ACTIVE_CHALLENGE = 6, RC_CLIENT_ACHIEVEMENT_BUCKET_ACTIVE_CHALLENGE = 6,
RC_CLIENT_ACHIEVEMENT_BUCKET_ALMOST_THERE = 7 RC_CLIENT_ACHIEVEMENT_BUCKET_ALMOST_THERE = 7,
NUM_RC_CLIENT_ACHIEVEMENT_BUCKETS = 8
}; };
enum { enum {
@ -370,6 +373,11 @@ rc_client_achievement_list_t* rc_client_create_achievement_list(rc_client_t* cli
*/ */
void rc_client_destroy_achievement_list(rc_client_achievement_list_t* list); void rc_client_destroy_achievement_list(rc_client_achievement_list_t* list);
/**
* Returns non-zero if there are any achievements that can be queried through rc_client_create_achievement_list().
*/
int rc_client_has_achievements(rc_client_t* client);
/*****************************************************************************\ /*****************************************************************************\
| Leaderboards | | Leaderboards |
\*****************************************************************************/ \*****************************************************************************/
@ -378,15 +386,26 @@ enum {
RC_CLIENT_LEADERBOARD_STATE_INACTIVE = 0, RC_CLIENT_LEADERBOARD_STATE_INACTIVE = 0,
RC_CLIENT_LEADERBOARD_STATE_ACTIVE = 1, RC_CLIENT_LEADERBOARD_STATE_ACTIVE = 1,
RC_CLIENT_LEADERBOARD_STATE_TRACKING = 2, RC_CLIENT_LEADERBOARD_STATE_TRACKING = 2,
RC_CLIENT_LEADERBOARD_STATE_DISABLED = 3 RC_CLIENT_LEADERBOARD_STATE_DISABLED = 3,
NUM_RC_CLIENT_LEADERBOARD_STATES = 4
}; };
enum {
RC_CLIENT_LEADERBOARD_FORMAT_TIME = 0,
RC_CLIENT_LEADERBOARD_FORMAT_SCORE = 1,
RC_CLIENT_LEADERBOARD_FORMAT_VALUE = 2,
NUM_RC_CLIENT_LEADERBOARD_FORMATS = 3
};
#define RC_CLIENT_LEADERBOARD_DISPLAY_SIZE 24
typedef struct rc_client_leaderboard_t { typedef struct rc_client_leaderboard_t {
const char* title; const char* title;
const char* description; const char* description;
const char* tracker_value; const char* tracker_value;
uint32_t id; uint32_t id;
uint8_t state; uint8_t state;
uint8_t format;
uint8_t lower_is_better; uint8_t lower_is_better;
} rc_client_leaderboard_t; } rc_client_leaderboard_t;
@ -396,7 +415,7 @@ typedef struct rc_client_leaderboard_t {
const rc_client_leaderboard_t* rc_client_get_leaderboard_info(const rc_client_t* client, uint32_t id); const rc_client_leaderboard_t* rc_client_get_leaderboard_info(const rc_client_t* client, uint32_t id);
typedef struct rc_client_leaderboard_tracker_t { typedef struct rc_client_leaderboard_tracker_t {
char display[24]; char display[RC_CLIENT_LEADERBOARD_DISPLAY_SIZE];
uint32_t id; uint32_t id;
} rc_client_leaderboard_tracker_t; } rc_client_leaderboard_tracker_t;
@ -419,7 +438,8 @@ enum {
RC_CLIENT_LEADERBOARD_BUCKET_INACTIVE = 1, RC_CLIENT_LEADERBOARD_BUCKET_INACTIVE = 1,
RC_CLIENT_LEADERBOARD_BUCKET_ACTIVE = 2, RC_CLIENT_LEADERBOARD_BUCKET_ACTIVE = 2,
RC_CLIENT_LEADERBOARD_BUCKET_UNSUPPORTED = 3, RC_CLIENT_LEADERBOARD_BUCKET_UNSUPPORTED = 3,
RC_CLIENT_LEADERBOARD_BUCKET_ALL = 4 RC_CLIENT_LEADERBOARD_BUCKET_ALL = 4,
NUM_RC_CLIENT_LEADERBOARD_BUCKETS = 5
}; };
enum { enum {
@ -438,9 +458,14 @@ rc_client_leaderboard_list_t* rc_client_create_leaderboard_list(rc_client_t* cli
*/ */
void rc_client_destroy_leaderboard_list(rc_client_leaderboard_list_t* list); void rc_client_destroy_leaderboard_list(rc_client_leaderboard_list_t* list);
/**
* Returns non-zero if the current game has any leaderboards.
*/
int rc_client_has_leaderboards(rc_client_t* client);
typedef struct rc_client_leaderboard_entry_t { typedef struct rc_client_leaderboard_entry_t {
const char* user; const char* user;
char display[24]; char display[RC_CLIENT_LEADERBOARD_DISPLAY_SIZE];
time_t submitted; time_t submitted;
uint32_t rank; uint32_t rank;
uint32_t index; uint32_t index;
@ -480,10 +505,46 @@ int rc_client_leaderboard_entry_get_user_image_url(const rc_client_leaderboard_e
*/ */
void rc_client_destroy_leaderboard_entry_list(rc_client_leaderboard_entry_list_t* list); void rc_client_destroy_leaderboard_entry_list(rc_client_leaderboard_entry_list_t* list);
/**
* Used for scoreboard events. Contains the response from the server when a leaderboard entry is submitted.
* NOTE: This structure is only valid within the event callback. If you want to make use of the data outside
* of the callback, you should create copies of both the top entries and usernames within.
*/
typedef struct rc_client_leaderboard_scoreboard_entry_t {
/* The user associated to the entry */
const char* username;
/* The rank of the entry */
unsigned rank;
/* The value of the entry */
char score[RC_CLIENT_LEADERBOARD_DISPLAY_SIZE];
} rc_client_leaderboard_scoreboard_entry_t;
typedef struct rc_client_leaderboard_scoreboard_t {
/* The ID of the leaderboard which was submitted */
uint32_t leaderboard_id;
/* The value that was submitted */
char submitted_score[RC_CLIENT_LEADERBOARD_DISPLAY_SIZE];
/* The player's best submitted value */
char best_score[RC_CLIENT_LEADERBOARD_DISPLAY_SIZE];
/* The player's new rank within the leaderboard */
unsigned new_rank;
/* The total number of entries in the leaderboard */
unsigned num_entries;
/* An array of the top entries for the leaderboard */
rc_client_leaderboard_scoreboard_entry_t* top_entries;
/* The number of items in the top_entries array */
unsigned num_top_entries;
} rc_client_leaderboard_scoreboard_t;
/*****************************************************************************\ /*****************************************************************************\
| Rich Presence | | Rich Presence |
\*****************************************************************************/ \*****************************************************************************/
/**
* Returns non-zero if the current game supports rich presence.
*/
int rc_client_has_rich_presence(rc_client_t* client);
/** /**
* Gets the current rich presence message. * Gets the current rich presence message.
* Returns the number of characters written to buffer. * Returns the number of characters written to buffer.
@ -508,11 +569,12 @@ enum {
RC_CLIENT_EVENT_LEADERBOARD_TRACKER_SHOW = 10, /* [leaderboard_tracker] should be shown */ RC_CLIENT_EVENT_LEADERBOARD_TRACKER_SHOW = 10, /* [leaderboard_tracker] should be shown */
RC_CLIENT_EVENT_LEADERBOARD_TRACKER_HIDE = 11, /* [leaderboard_tracker] should be hidden */ RC_CLIENT_EVENT_LEADERBOARD_TRACKER_HIDE = 11, /* [leaderboard_tracker] should be hidden */
RC_CLIENT_EVENT_LEADERBOARD_TRACKER_UPDATE = 12, /* [leaderboard_tracker] updated */ RC_CLIENT_EVENT_LEADERBOARD_TRACKER_UPDATE = 12, /* [leaderboard_tracker] updated */
RC_CLIENT_EVENT_RESET = 13, /* emulated system should be reset (as the result of enabling hardcore) */ RC_CLIENT_EVENT_LEADERBOARD_SCOREBOARD = 13, /* [leaderboard_scoreboard] possibly-new ranking received */
RC_CLIENT_EVENT_GAME_COMPLETED = 14, /* all achievements for the game have been earned */ RC_CLIENT_EVENT_RESET = 14, /* emulated system should be reset (as the result of enabling hardcore) */
RC_CLIENT_EVENT_SERVER_ERROR = 15, /* an API response returned a [server_error] and will not be retried */ RC_CLIENT_EVENT_GAME_COMPLETED = 15, /* all achievements for the game have been earned */
RC_CLIENT_EVENT_DISCONNECTED = 16, /* an unlock request could not be completed and is pending */ RC_CLIENT_EVENT_SERVER_ERROR = 16, /* an API response returned a [server_error] and will not be retried */
RC_CLIENT_EVENT_RECONNECTED = 17 /* all pending unlocks have been completed */ RC_CLIENT_EVENT_DISCONNECTED = 17, /* an unlock request could not be completed and is pending */
RC_CLIENT_EVENT_RECONNECTED = 18 /* all pending unlocks have been completed */
}; };
typedef struct rc_client_server_error_t typedef struct rc_client_server_error_t
@ -528,6 +590,7 @@ typedef struct rc_client_event_t
rc_client_achievement_t* achievement; rc_client_achievement_t* achievement;
rc_client_leaderboard_t* leaderboard; rc_client_leaderboard_t* leaderboard;
rc_client_leaderboard_tracker_t* leaderboard_tracker; rc_client_leaderboard_tracker_t* leaderboard_tracker;
rc_client_leaderboard_scoreboard_t* leaderboard_scoreboard;
rc_client_server_error_t* server_error; rc_client_server_error_t* server_error;
} rc_client_event_t; } rc_client_event_t;

View File

@ -1433,6 +1433,31 @@ static void rc_client_copy_achievements(rc_client_load_state_t* load_state,
subset->achievements = achievements; subset->achievements = achievements;
} }
static uint8_t rc_client_map_leaderboard_format(const rc_api_leaderboard_definition_t* defn)
{
switch (defn->format) {
case RC_FORMAT_SECONDS:
case RC_FORMAT_CENTISECS:
case RC_FORMAT_MINUTES:
case RC_FORMAT_SECONDS_AS_MINUTES:
case RC_FORMAT_FRAMES:
return RC_CLIENT_LEADERBOARD_FORMAT_TIME;
case RC_FORMAT_SCORE:
return RC_CLIENT_LEADERBOARD_FORMAT_SCORE;
case RC_FORMAT_VALUE:
case RC_FORMAT_FLOAT1:
case RC_FORMAT_FLOAT2:
case RC_FORMAT_FLOAT3:
case RC_FORMAT_FLOAT4:
case RC_FORMAT_FLOAT5:
case RC_FORMAT_FLOAT6:
default:
return RC_CLIENT_LEADERBOARD_FORMAT_VALUE;
}
}
static void rc_client_copy_leaderboards(rc_client_load_state_t* load_state, static void rc_client_copy_leaderboards(rc_client_load_state_t* load_state,
rc_client_subset_info_t* subset, rc_client_subset_info_t* subset,
const rc_api_leaderboard_definition_t* leaderboard_definitions, uint32_t num_leaderboards) const rc_api_leaderboard_definition_t* leaderboard_definitions, uint32_t num_leaderboards)
@ -1477,6 +1502,7 @@ static void rc_client_copy_leaderboards(rc_client_load_state_t* load_state,
leaderboard->public_.title = rc_buf_strcpy(buffer, read->title); leaderboard->public_.title = rc_buf_strcpy(buffer, read->title);
leaderboard->public_.description = rc_buf_strcpy(buffer, read->description); leaderboard->public_.description = rc_buf_strcpy(buffer, read->description);
leaderboard->public_.id = read->id; leaderboard->public_.id = read->id;
leaderboard->public_.format = rc_client_map_leaderboard_format(read);
leaderboard->public_.lower_is_better = read->lower_is_better; leaderboard->public_.lower_is_better = read->lower_is_better;
leaderboard->format = (uint8_t)read->format; leaderboard->format = (uint8_t)read->format;
leaderboard->hidden = (uint8_t)read->hidden; leaderboard->hidden = (uint8_t)read->hidden;
@ -2706,6 +2732,34 @@ void rc_client_destroy_achievement_list(rc_client_achievement_list_t* list)
free(list); free(list);
} }
int rc_client_has_achievements(rc_client_t* client)
{
rc_client_subset_info_t* subset;
int result;
if (!client || !client->game)
return 0;
rc_mutex_lock(&client->state.mutex);
subset = client->game->subsets;
result = 0;
for (; subset; subset = subset->next)
{
if (!subset->active)
continue;
if (subset->public_.num_achievements > 0) {
result = 1;
break;
}
}
rc_mutex_unlock(&client->state.mutex);
return result;
}
static const rc_client_achievement_t* rc_client_subset_get_achievement_info( static const rc_client_achievement_t* rc_client_subset_get_achievement_info(
rc_client_t* client, rc_client_subset_info_t* subset, uint32_t id) rc_client_t* client, rc_client_subset_info_t* subset, uint32_t id)
{ {
@ -2989,14 +3043,14 @@ static void rc_client_reset_achievements(rc_client_t* client)
/* ===== Leaderboards ===== */ /* ===== Leaderboards ===== */
static const rc_client_leaderboard_t* rc_client_subset_get_leaderboard_info(const rc_client_subset_info_t* subset, uint32_t id) static rc_client_leaderboard_info_t* rc_client_subset_get_leaderboard_info(const rc_client_subset_info_t* subset, uint32_t id)
{ {
rc_client_leaderboard_info_t* leaderboard = subset->leaderboards; rc_client_leaderboard_info_t* leaderboard = subset->leaderboards;
rc_client_leaderboard_info_t* stop = leaderboard + subset->public_.num_leaderboards; rc_client_leaderboard_info_t* stop = leaderboard + subset->public_.num_leaderboards;
for (; leaderboard < stop; ++leaderboard) { for (; leaderboard < stop; ++leaderboard) {
if (leaderboard->public_.id == id) if (leaderboard->public_.id == id)
return &leaderboard->public_; return leaderboard;
} }
return NULL; return NULL;
@ -3010,9 +3064,9 @@ const rc_client_leaderboard_t* rc_client_get_leaderboard_info(const rc_client_t*
return NULL; return NULL;
for (subset = client->game->subsets; subset; subset = subset->next) { for (subset = client->game->subsets; subset; subset = subset->next) {
const rc_client_leaderboard_t* leaderboard = rc_client_subset_get_leaderboard_info(subset, id); const rc_client_leaderboard_info_t* leaderboard = rc_client_subset_get_leaderboard_info(subset, id);
if (leaderboard != NULL) if (leaderboard != NULL)
return leaderboard; return &leaderboard->public_;
} }
return NULL; return NULL;
@ -3242,6 +3296,34 @@ void rc_client_destroy_leaderboard_list(rc_client_leaderboard_list_t* list)
free(list); free(list);
} }
int rc_client_has_leaderboards(rc_client_t* client)
{
rc_client_subset_info_t* subset;
int result;
if (!client || !client->game)
return 0;
rc_mutex_lock(&client->state.mutex);
subset = client->game->subsets;
result = 0;
for (; subset; subset = subset->next)
{
if (!subset->active)
continue;
if (subset->public_.num_leaderboards > 0) {
result = 1;
break;
}
}
rc_mutex_unlock(&client->state.mutex);
return result;
}
static void rc_client_allocate_leaderboard_tracker(rc_client_game_info_t* game, rc_client_leaderboard_info_t* leaderboard) static void rc_client_allocate_leaderboard_tracker(rc_client_game_info_t* game, rc_client_leaderboard_info_t* leaderboard)
{ {
rc_client_leaderboard_tracker_info_t* tracker; rc_client_leaderboard_tracker_info_t* tracker;
@ -3345,6 +3427,61 @@ static void rc_client_submit_leaderboard_entry_retry(rc_client_scheduled_callbac
rc_client_submit_leaderboard_entry_server_call(lboard_data); rc_client_submit_leaderboard_entry_server_call(lboard_data);
} }
static void rc_client_raise_scoreboard_event(rc_client_submit_leaderboard_entry_callback_data_t* lboard_data,
const rc_api_submit_lboard_entry_response_t* response)
{
rc_client_leaderboard_scoreboard_t sboard;
rc_client_event_t client_event;
rc_client_subset_info_t* subset;
rc_client_t* client = lboard_data->client;
rc_client_leaderboard_info_t* leaderboard = NULL;
if (!client || !client->game)
return;
for (subset = client->game->subsets; subset; subset = subset->next) {
leaderboard = rc_client_subset_get_leaderboard_info(subset, lboard_data->id);
if (leaderboard != NULL)
break;
}
if (leaderboard == NULL) {
RC_CLIENT_LOG_ERR_FORMATTED(client, "Trying to raise scoreboard for unknown leaderboard %u", lboard_data->id);
return;
}
memset(&sboard, 0, sizeof(sboard));
sboard.leaderboard_id = lboard_data->id;
rc_format_value(sboard.submitted_score, sizeof(sboard.submitted_score), response->submitted_score, leaderboard->format);
rc_format_value(sboard.best_score, sizeof(sboard.best_score), response->best_score, leaderboard->format);
sboard.new_rank = response->new_rank;
sboard.num_entries = response->num_entries;
sboard.num_top_entries = response->num_top_entries;
if (sboard.num_top_entries > 0) {
sboard.top_entries = (rc_client_leaderboard_scoreboard_entry_t*)calloc(
response->num_top_entries, sizeof(rc_client_leaderboard_scoreboard_entry_t));
if (sboard.top_entries != NULL) {
unsigned i;
for (i = 0; i < response->num_top_entries; i++) {
sboard.top_entries[i].username = response->top_entries[i].username;
sboard.top_entries[i].rank = response->top_entries[i].rank;
rc_format_value(sboard.top_entries[i].score, sizeof(sboard.top_entries[i].score), response->top_entries[i].score,
leaderboard->format);
}
}
}
memset(&client_event, 0, sizeof(client_event));
client_event.type = RC_CLIENT_EVENT_LEADERBOARD_SCOREBOARD;
client_event.leaderboard = &leaderboard->public_;
client_event.leaderboard_scoreboard = &sboard;
lboard_data->client->callbacks.event_handler(&client_event, lboard_data->client);
if (sboard.top_entries != NULL) {
free(sboard.top_entries);
}
}
static void rc_client_submit_leaderboard_entry_callback(const rc_api_server_response_t* server_response, void* callback_data) static void rc_client_submit_leaderboard_entry_callback(const rc_api_server_response_t* server_response, void* callback_data)
{ {
rc_client_submit_leaderboard_entry_callback_data_t* lboard_data = rc_client_submit_leaderboard_entry_callback_data_t* lboard_data =
@ -3394,7 +3531,10 @@ static void rc_client_submit_leaderboard_entry_callback(const rc_api_server_resp
} }
} }
else { else {
/* TODO: raise event for scoreboard (if retry_count < 2) */ /* raise event for scoreboard */
if (lboard_data->retry_count < 2) {
rc_client_raise_scoreboard_event(lboard_data, &submit_lboard_entry_response);
}
/* not currently doing anything with the response */ /* not currently doing anything with the response */
if (lboard_data->retry_count) { if (lboard_data->retry_count) {
@ -3700,6 +3840,17 @@ static void rc_client_ping(rc_client_scheduled_callback_data_t* callback_data, r
rc_client_schedule_callback(client, callback_data); rc_client_schedule_callback(client, callback_data);
} }
int rc_client_has_rich_presence(rc_client_t* client)
{
if (!client || !client->game)
return 0;
if (!client->game->runtime.richpresence || !client->game->runtime.richpresence)
return 0;
return 1;
}
size_t rc_client_get_rich_presence_message(rc_client_t* client, char buffer[], size_t buffer_size) size_t rc_client_get_rich_presence_message(rc_client_t* client, char buffer[], size_t buffer_size)
{ {
int result; int result;