2024-08-18 08:55:31 +08:00
/* Copyright (C) 2019 Mr Goldberg
This file is part of the Goldberg Emulator
The Goldberg Emulator is free software ; you can redistribute it and / or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation ; either
version 3 of the License , or ( at your option ) any later version .
The Goldberg Emulator is distributed in the hope that it will be useful ,
but WITHOUT ANY WARRANTY ; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the GNU
Lesser General Public License for more details .
You should have received a copy of the GNU Lesser General Public
License along with the Goldberg Emulator ; if not , see
< http : //www.gnu.org/licenses/>. */
# include "dll/steam_user_stats.h"
# include <random>
// --- Steam_Leaderboard ---
Steam_Leaderboard_Entry * Steam_Leaderboard : : find_recent_entry ( const CSteamID & steamid ) const
{
auto my_it = std : : find_if ( entries . begin ( ) , entries . end ( ) , [ & steamid ] ( const Steam_Leaderboard_Entry & item ) {
return item . steam_id = = steamid ;
} ) ;
if ( entries . end ( ) = = my_it ) return nullptr ;
return const_cast < Steam_Leaderboard_Entry * > ( & * my_it ) ;
}
void Steam_Leaderboard : : remove_entries ( const CSteamID & steamid )
{
auto rm_it = std : : remove_if ( entries . begin ( ) , entries . end ( ) , [ & ] ( const Steam_Leaderboard_Entry & item ) {
return item . steam_id = = steamid ;
} ) ;
if ( entries . end ( ) ! = rm_it ) entries . erase ( rm_it , entries . end ( ) ) ;
}
void Steam_Leaderboard : : remove_duplicate_entries ( )
{
if ( entries . size ( ) < = 1 ) return ;
auto rm = std : : remove_if ( entries . begin ( ) , entries . end ( ) , [ & ] ( const Steam_Leaderboard_Entry & item ) {
auto recent = find_recent_entry ( item . steam_id ) ;
return & item ! = recent ;
} ) ;
if ( entries . end ( ) ! = rm ) entries . erase ( rm , entries . end ( ) ) ;
}
void Steam_Leaderboard : : sort_entries ( )
{
if ( sort_method = = k_ELeaderboardSortMethodNone ) return ;
if ( entries . size ( ) < = 1 ) return ;
std : : sort ( entries . begin ( ) , entries . end ( ) , [ this ] ( const Steam_Leaderboard_Entry & item1 , const Steam_Leaderboard_Entry & item2 ) {
if ( sort_method = = k_ELeaderboardSortMethodAscending ) {
return item1 . score < item2 . score ;
} else { // k_ELeaderboardSortMethodDescending
return item1 . score > item2 . score ;
}
} ) ;
}
// --- Steam_Leaderboard ---
/*
layout of each item in the leaderboard file
| steamid - lower 32 - bits | steamid - higher 32 - bits | score ( 4 bytes ) | score details count ( 4 bytes ) | score details array ( 4 bytes each ) . . .
[ 0 ] | [ 1 ] | [ 2 ] | [ 3 ] | [ 4 ]
^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ main header ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^
*/
std : : vector < Steam_Leaderboard_Entry > Steam_User_Stats : : load_leaderboard_entries ( const std : : string & name )
{
constexpr const static unsigned int MAIN_HEADER_ELEMENTS_COUNT = 4 ;
constexpr const static unsigned int ELEMENT_SIZE = ( unsigned int ) sizeof ( uint32_t ) ;
std : : vector < Steam_Leaderboard_Entry > out { } ;
std : : string leaderboard_name ( common_helpers : : ascii_to_lowercase ( name ) ) ;
unsigned read_bytes = local_storage - > file_size ( Local_Storage : : leaderboard_storage_folder , leaderboard_name ) ;
if ( ( read_bytes = = 0 ) | |
( read_bytes < ( ELEMENT_SIZE * MAIN_HEADER_ELEMENTS_COUNT ) ) | |
( read_bytes % ELEMENT_SIZE ) ! = 0 ) {
return out ;
}
std : : vector < uint32_t > output ( read_bytes / ELEMENT_SIZE ) ;
if ( local_storage - > get_data ( Local_Storage : : leaderboard_storage_folder , leaderboard_name , ( char * ) output . data ( ) , read_bytes ) ! = read_bytes ) return out ;
unsigned int i = 0 ;
while ( true ) {
if ( ( i + MAIN_HEADER_ELEMENTS_COUNT ) > output . size ( ) ) break ; // invalid main header, or end of buffer
Steam_Leaderboard_Entry new_entry { } ;
new_entry . steam_id = CSteamID ( ( uint64 ) output [ i ] + ( ( ( uint64 ) output [ i + 1 ] ) < < 32 ) ) ;
new_entry . score = ( int32 ) output [ i + 2 ] ;
uint32_t details_count = output [ i + 3 ] ;
i + = MAIN_HEADER_ELEMENTS_COUNT ; // skip main header
if ( ( i + details_count ) > output . size ( ) ) break ; // invalid score details count
for ( uint32_t j = 0 ; j < details_count ; + + j ) {
new_entry . score_details . push_back ( output [ i ] ) ;
+ + i ; // move past this score detail
}
PRINT_DEBUG ( " '%s': user %llu, score %i, details count = %zu " ,
name . c_str ( ) , new_entry . steam_id . ConvertToUint64 ( ) , new_entry . score , new_entry . score_details . size ( )
) ;
out . push_back ( new_entry ) ;
}
PRINT_DEBUG ( " '%s' total entries = %zu " , name . c_str ( ) , out . size ( ) ) ;
return out ;
}
void Steam_User_Stats : : save_my_leaderboard_entry ( const Steam_Leaderboard & leaderboard )
{
auto my_entry = leaderboard . find_recent_entry ( settings - > get_local_steam_id ( ) ) ;
if ( ! my_entry ) return ; // we don't have a score entry
PRINT_DEBUG ( " saving entries for leaderboard '%s' " , leaderboard . name . c_str ( ) ) ;
std : : vector < uint32_t > output { } ;
uint64_t steam_id = my_entry - > steam_id . ConvertToUint64 ( ) ;
output . push_back ( ( uint32_t ) ( steam_id & 0xFFFFFFFF ) ) ; // lower 4 bytes
output . push_back ( ( uint32_t ) ( steam_id > > 32 ) ) ; // higher 4 bytes
output . push_back ( my_entry - > score ) ;
output . push_back ( ( uint32_t ) my_entry - > score_details . size ( ) ) ;
for ( const auto & detail : my_entry - > score_details ) {
output . push_back ( detail ) ;
}
std : : string leaderboard_name ( common_helpers : : ascii_to_lowercase ( leaderboard . name ) ) ;
unsigned int buffer_size = static_cast < unsigned int > ( output . size ( ) * sizeof ( output [ 0 ] ) ) ; // in bytes
local_storage - > store_data ( Local_Storage : : leaderboard_storage_folder , leaderboard_name , ( char * ) & output [ 0 ] , buffer_size ) ;
}
Steam_Leaderboard_Entry * Steam_User_Stats : : update_leaderboard_entry ( Steam_Leaderboard & leaderboard , const Steam_Leaderboard_Entry & entry , bool overwrite )
{
bool added = false ;
auto user_entry = leaderboard . find_recent_entry ( entry . steam_id ) ;
if ( ! user_entry ) { // user doesn't have an entry yet, create one
added = true ;
leaderboard . entries . push_back ( entry ) ;
user_entry = & leaderboard . entries . back ( ) ;
} else if ( overwrite ) {
added = true ;
* user_entry = entry ;
}
if ( added ) { // if we added a new entry then we have to sort and find the target entry again
leaderboard . sort_entries ( ) ;
user_entry = leaderboard . find_recent_entry ( entry . steam_id ) ;
PRINT_DEBUG ( " added/updated entry for user %llu " , entry . steam_id . ConvertToUint64 ( ) ) ;
}
return user_entry ;
}
unsigned int Steam_User_Stats : : find_cached_leaderboard ( const std : : string & name )
{
unsigned index = 1 ;
for ( const auto & leaderboard : cached_leaderboards ) {
if ( common_helpers : : str_cmp_insensitive ( leaderboard . name , name ) ) return index ;
+ + index ;
}
return 0 ;
}
unsigned int Steam_User_Stats : : cache_leaderboard_ifneeded ( const std : : string & name , ELeaderboardSortMethod eLeaderboardSortMethod , ELeaderboardDisplayType eLeaderboardDisplayType )
{
unsigned int board_handle = find_cached_leaderboard ( name ) ;
if ( board_handle ) return board_handle ;
// PRINT_DEBUG("cache miss '%s'", name.c_str());
// create a new entry in-memory and try reading the entries from disk
struct Steam_Leaderboard new_board { } ;
2024-09-14 12:59:57 +08:00
new_board . name = name ;
2024-08-18 08:55:31 +08:00
new_board . sort_method = eLeaderboardSortMethod ;
new_board . display_type = eLeaderboardDisplayType ;
new_board . entries = load_leaderboard_entries ( name ) ;
new_board . sort_entries ( ) ;
new_board . remove_duplicate_entries ( ) ;
// save it in memory for later
cached_leaderboards . push_back ( new_board ) ;
board_handle = static_cast < unsigned int > ( cached_leaderboards . size ( ) ) ;
PRINT_DEBUG ( " cached a new leaderboard '%s' %i %i " ,
new_board . name . c_str ( ) , ( int ) eLeaderboardSortMethod , ( int ) eLeaderboardDisplayType
) ;
return board_handle ;
}
void Steam_User_Stats : : send_my_leaderboard_score ( const Steam_Leaderboard & board , const CSteamID * steamid , bool want_scores_back )
{
if ( ! settings - > share_leaderboards_over_network ) return ;
const auto my_entry = board . find_recent_entry ( settings - > get_local_steam_id ( ) ) ;
Leaderboards_Messages : : UserScoreEntry * score_entry_msg = nullptr ;
if ( my_entry ) {
score_entry_msg = new Leaderboards_Messages : : UserScoreEntry ( ) ;
score_entry_msg - > set_score ( my_entry - > score ) ;
score_entry_msg - > mutable_score_details ( ) - > Assign ( my_entry - > score_details . begin ( ) , my_entry - > score_details . end ( ) ) ;
}
auto board_info_msg = new Leaderboards_Messages : : LeaderboardInfo ( ) ;
board_info_msg - > set_allocated_board_name ( new std : : string ( board . name ) ) ;
board_info_msg - > set_sort_method ( board . sort_method ) ;
board_info_msg - > set_display_type ( board . display_type ) ;
auto board_msg = new Leaderboards_Messages ( ) ;
if ( want_scores_back ) board_msg - > set_type ( Leaderboards_Messages : : UpdateUserScoreMutual ) ;
else board_msg - > set_type ( Leaderboards_Messages : : UpdateUserScore ) ;
board_msg - > set_appid ( settings - > get_local_game_id ( ) . AppID ( ) ) ;
board_msg - > set_allocated_leaderboard_info ( board_info_msg ) ;
// if we have an entry
if ( score_entry_msg ) board_msg - > set_allocated_user_score_entry ( score_entry_msg ) ;
Common_Message common_msg { } ;
common_msg . set_source_id ( settings - > get_local_steam_id ( ) . ConvertToUint64 ( ) ) ;
if ( steamid ) common_msg . set_dest_id ( steamid - > ConvertToUint64 ( ) ) ;
common_msg . set_allocated_leaderboards_messages ( board_msg ) ;
if ( steamid ) network - > sendTo ( & common_msg , false ) ;
else network - > sendToAll ( & common_msg , false ) ;
}
void Steam_User_Stats : : request_user_leaderboard_entry ( const Steam_Leaderboard & board , const CSteamID & steamid )
{
if ( ! settings - > share_leaderboards_over_network ) return ;
auto board_info_msg = new Leaderboards_Messages : : LeaderboardInfo ( ) ;
board_info_msg - > set_allocated_board_name ( new std : : string ( board . name ) ) ;
board_info_msg - > set_sort_method ( board . sort_method ) ;
board_info_msg - > set_display_type ( board . display_type ) ;
auto board_msg = new Leaderboards_Messages ( ) ;
board_msg - > set_type ( Leaderboards_Messages : : RequestUserScore ) ;
board_msg - > set_appid ( settings - > get_local_game_id ( ) . AppID ( ) ) ;
board_msg - > set_allocated_leaderboard_info ( board_info_msg ) ;
Common_Message common_msg { } ;
common_msg . set_source_id ( settings - > get_local_steam_id ( ) . ConvertToUint64 ( ) ) ;
common_msg . set_dest_id ( steamid . ConvertToUint64 ( ) ) ;
common_msg . set_allocated_leaderboards_messages ( board_msg ) ;
network - > sendTo ( & common_msg , false ) ;
}
void Steam_User_Stats : : steam_user_stats_network_leaderboards ( void * object , Common_Message * msg )
{
// PRINT_DEBUG_ENTRY();
auto inst = ( Steam_User_Stats * ) object ;
inst - > network_callback_leaderboards ( msg ) ;
}
// Leaderboard functions
// asks the Steam back-end for a leaderboard by name, and will create it if it's not yet
// This call is asynchronous, with the result returned in LeaderboardFindResult_t
STEAM_CALL_RESULT ( LeaderboardFindResult_t )
SteamAPICall_t Steam_User_Stats : : FindOrCreateLeaderboard ( const char * pchLeaderboardName , ELeaderboardSortMethod eLeaderboardSortMethod , ELeaderboardDisplayType eLeaderboardDisplayType )
{
PRINT_DEBUG ( " '%s' " , pchLeaderboardName ) ;
std : : lock_guard < std : : recursive_mutex > lock ( global_mutex ) ;
if ( ! pchLeaderboardName ) {
LeaderboardFindResult_t data { } ;
data . m_hSteamLeaderboard = 0 ;
data . m_bLeaderboardFound = 0 ;
auto ret = callback_results - > addCallResult ( data . k_iCallback , & data , sizeof ( data ) ) ;
callbacks - > addCBResult ( data . k_iCallback , & data , sizeof ( data ) ) ;
return ret ;
}
unsigned int board_handle = cache_leaderboard_ifneeded ( pchLeaderboardName , eLeaderboardSortMethod , eLeaderboardDisplayType ) ;
send_my_leaderboard_score ( cached_leaderboards [ board_handle - 1 ] , nullptr , true ) ;
LeaderboardFindResult_t data { } ;
data . m_hSteamLeaderboard = board_handle ;
data . m_bLeaderboardFound = 1 ;
auto ret = callback_results - > addCallResult ( data . k_iCallback , & data , sizeof ( data ) , 0.1 ) ; // TODO is the timing ok?
callbacks - > addCBResult ( data . k_iCallback , & data , sizeof ( data ) , 0.1 ) ;
return ret ;
}
// as above, but won't create the leaderboard if it's not found
// This call is asynchronous, with the result returned in LeaderboardFindResult_t
STEAM_CALL_RESULT ( LeaderboardFindResult_t )
SteamAPICall_t Steam_User_Stats : : FindLeaderboard ( const char * pchLeaderboardName )
{
PRINT_DEBUG ( " '%s' " , pchLeaderboardName ) ;
std : : lock_guard < std : : recursive_mutex > lock ( global_mutex ) ;
if ( ! pchLeaderboardName ) {
LeaderboardFindResult_t data { } ;
data . m_hSteamLeaderboard = 0 ;
data . m_bLeaderboardFound = 0 ;
auto ret = callback_results - > addCallResult ( data . k_iCallback , & data , sizeof ( data ) ) ;
callbacks - > addCBResult ( data . k_iCallback , & data , sizeof ( data ) ) ;
return ret ;
}
std : : string name_lower ( common_helpers : : ascii_to_lowercase ( pchLeaderboardName ) ) ;
const auto & settings_Leaderboards = settings - > getLeaderboards ( ) ;
auto it = settings_Leaderboards . begin ( ) ;
for ( ; settings_Leaderboards . end ( ) ! = it ; + + it ) {
if ( common_helpers : : str_cmp_insensitive ( it - > first , name_lower ) ) break ;
}
if ( settings_Leaderboards . end ( ) ! = it ) {
auto & config = it - > second ;
return FindOrCreateLeaderboard ( pchLeaderboardName , config . sort_method , config . display_type ) ;
} else if ( ! settings - > disable_leaderboards_create_unknown ) {
return FindOrCreateLeaderboard ( pchLeaderboardName , k_ELeaderboardSortMethodDescending , k_ELeaderboardDisplayTypeNumeric ) ;
} else {
LeaderboardFindResult_t data { } ;
data . m_hSteamLeaderboard = find_cached_leaderboard ( name_lower ) ;
data . m_bLeaderboardFound = ! ! data . m_hSteamLeaderboard ;
auto ret = callback_results - > addCallResult ( data . k_iCallback , & data , sizeof ( data ) ) ;
callbacks - > addCBResult ( data . k_iCallback , & data , sizeof ( data ) ) ;
return ret ;
}
}
// returns the name of a leaderboard
const char * Steam_User_Stats : : GetLeaderboardName ( SteamLeaderboard_t hSteamLeaderboard )
{
PRINT_DEBUG ( " %llu " , hSteamLeaderboard ) ;
std : : lock_guard < std : : recursive_mutex > lock ( global_mutex ) ;
if ( hSteamLeaderboard > cached_leaderboards . size ( ) | | hSteamLeaderboard < = 0 ) return " " ;
return cached_leaderboards [ static_cast < unsigned > ( hSteamLeaderboard - 1 ) ] . name . c_str ( ) ;
}
// returns the total number of entries in a leaderboard, as of the last request
int Steam_User_Stats : : GetLeaderboardEntryCount ( SteamLeaderboard_t hSteamLeaderboard )
{
PRINT_DEBUG ( " %llu " , hSteamLeaderboard ) ;
std : : lock_guard < std : : recursive_mutex > lock ( global_mutex ) ;
if ( hSteamLeaderboard > cached_leaderboards . size ( ) | | hSteamLeaderboard < = 0 ) return 0 ;
return ( int ) cached_leaderboards [ static_cast < unsigned > ( hSteamLeaderboard - 1 ) ] . entries . size ( ) ;
}
// returns the sort method of the leaderboard
ELeaderboardSortMethod Steam_User_Stats : : GetLeaderboardSortMethod ( SteamLeaderboard_t hSteamLeaderboard )
{
PRINT_DEBUG ( " %llu " , hSteamLeaderboard ) ;
std : : lock_guard < std : : recursive_mutex > lock ( global_mutex ) ;
if ( hSteamLeaderboard > cached_leaderboards . size ( ) | | hSteamLeaderboard < = 0 ) return k_ELeaderboardSortMethodNone ;
return cached_leaderboards [ static_cast < unsigned > ( hSteamLeaderboard - 1 ) ] . sort_method ;
}
// returns the display type of the leaderboard
ELeaderboardDisplayType Steam_User_Stats : : GetLeaderboardDisplayType ( SteamLeaderboard_t hSteamLeaderboard )
{
PRINT_DEBUG ( " %llu " , hSteamLeaderboard ) ;
std : : lock_guard < std : : recursive_mutex > lock ( global_mutex ) ;
if ( hSteamLeaderboard > cached_leaderboards . size ( ) | | hSteamLeaderboard < = 0 ) return k_ELeaderboardDisplayTypeNone ;
return cached_leaderboards [ static_cast < unsigned > ( hSteamLeaderboard - 1 ) ] . display_type ;
}
// Asks the Steam back-end for a set of rows in the leaderboard.
// This call is asynchronous, with the result returned in LeaderboardScoresDownloaded_t
// LeaderboardScoresDownloaded_t will contain a handle to pull the results from GetDownloadedLeaderboardEntries() (below)
// You can ask for more entries than exist, and it will return as many as do exist.
// k_ELeaderboardDataRequestGlobal requests rows in the leaderboard from the full table, with nRangeStart & nRangeEnd in the range [1, TotalEntries]
// k_ELeaderboardDataRequestGlobalAroundUser requests rows around the current user, nRangeStart being negate
// e.g. DownloadLeaderboardEntries( hLeaderboard, k_ELeaderboardDataRequestGlobalAroundUser, -3, 3 ) will return 7 rows, 3 before the user, 3 after
// k_ELeaderboardDataRequestFriends requests all the rows for friends of the current user
STEAM_CALL_RESULT ( LeaderboardScoresDownloaded_t )
SteamAPICall_t Steam_User_Stats : : DownloadLeaderboardEntries ( SteamLeaderboard_t hSteamLeaderboard , ELeaderboardDataRequest eLeaderboardDataRequest , int nRangeStart , int nRangeEnd )
{
PRINT_DEBUG ( " %llu %i [%i, %i] " , hSteamLeaderboard , eLeaderboardDataRequest , nRangeStart , nRangeEnd ) ;
std : : lock_guard < std : : recursive_mutex > lock ( global_mutex ) ;
if ( hSteamLeaderboard > cached_leaderboards . size ( ) | | hSteamLeaderboard < = 0 ) return k_uAPICallInvalid ; //might return callresult even if hSteamLeaderboard is invalid
int entries_count = ( int ) cached_leaderboards [ static_cast < unsigned > ( hSteamLeaderboard - 1 ) ] . entries . size ( ) ;
// https://partner.steamgames.com/doc/api/ISteamUserStats#ELeaderboardDataRequest
if ( eLeaderboardDataRequest ! = k_ELeaderboardDataRequestFriends ) {
int required_count = nRangeEnd - nRangeStart + 1 ;
if ( required_count < 0 ) required_count = 0 ;
if ( required_count < entries_count ) entries_count = required_count ;
}
LeaderboardScoresDownloaded_t data { } ;
data . m_hSteamLeaderboard = hSteamLeaderboard ;
data . m_hSteamLeaderboardEntries = hSteamLeaderboard ;
data . m_cEntryCount = entries_count ;
auto ret = callback_results - > addCallResult ( data . k_iCallback , & data , sizeof ( data ) , 0.1 ) ; // TODO is this timing ok?
callbacks - > addCBResult ( data . k_iCallback , & data , sizeof ( data ) , 0.1 ) ;
return ret ;
}
// as above, but downloads leaderboard entries for an arbitrary set of users - ELeaderboardDataRequest is k_ELeaderboardDataRequestUsers
// if a user doesn't have a leaderboard entry, they won't be included in the result
// a max of 100 users can be downloaded at a time, with only one outstanding call at a time
STEAM_METHOD_DESC ( Downloads leaderboard entries for an arbitrary set of users - ELeaderboardDataRequest is k_ELeaderboardDataRequestUsers )
STEAM_CALL_RESULT ( LeaderboardScoresDownloaded_t )
SteamAPICall_t Steam_User_Stats : : DownloadLeaderboardEntriesForUsers ( SteamLeaderboard_t hSteamLeaderboard ,
STEAM_ARRAY_COUNT_D ( cUsers , Array of users to retrieve ) CSteamID * prgUsers , int cUsers )
{
PRINT_DEBUG ( " %i %llu " , cUsers , cUsers > 0 ? prgUsers [ 0 ] . ConvertToUint64 ( ) : 0 ) ;
std : : lock_guard < std : : recursive_mutex > lock ( global_mutex ) ;
if ( hSteamLeaderboard > cached_leaderboards . size ( ) | | hSteamLeaderboard < = 0 ) return k_uAPICallInvalid ; //might return callresult even if hSteamLeaderboard is invalid
auto & board = cached_leaderboards [ static_cast < unsigned > ( hSteamLeaderboard - 1 ) ] ;
bool ok = true ;
int total_count = 0 ;
if ( prgUsers & & cUsers > 0 ) {
for ( int i = 0 ; i < cUsers ; + + i ) {
const auto & user_steamid = prgUsers [ i ] ;
if ( ! user_steamid . IsValid ( ) ) {
ok = false ;
PRINT_DEBUG ( " bad userid %llu " , user_steamid . ConvertToUint64 ( ) ) ;
break ;
}
if ( board . find_recent_entry ( user_steamid ) ) + + total_count ;
request_user_leaderboard_entry ( board , user_steamid ) ;
}
}
PRINT_DEBUG ( " total count %i " , total_count ) ;
// https://partner.steamgames.com/doc/api/ISteamUserStats#DownloadLeaderboardEntriesForUsers
if ( ! ok | | total_count > 100 ) return k_uAPICallInvalid ;
LeaderboardScoresDownloaded_t data { } ;
data . m_hSteamLeaderboard = hSteamLeaderboard ;
data . m_hSteamLeaderboardEntries = hSteamLeaderboard ;
data . m_cEntryCount = total_count ;
auto ret = callback_results - > addCallResult ( data . k_iCallback , & data , sizeof ( data ) , 0.1 ) ; // TODO is this timing ok?
callbacks - > addCBResult ( data . k_iCallback , & data , sizeof ( data ) , 0.1 ) ;
return ret ;
}
// Returns data about a single leaderboard entry
// use a for loop from 0 to LeaderboardScoresDownloaded_t::m_cEntryCount to get all the downloaded entries
// e.g.
// void OnLeaderboardScoresDownloaded( LeaderboardScoresDownloaded_t *pLeaderboardScoresDownloaded )
// {
// for ( int index = 0; index < pLeaderboardScoresDownloaded->m_cEntryCount; index++ )
// {
// LeaderboardEntry_t leaderboardEntry;
// int32 details[3]; // we know this is how many we've stored previously
// GetDownloadedLeaderboardEntry( pLeaderboardScoresDownloaded->m_hSteamLeaderboardEntries, index, &leaderboardEntry, details, 3 );
// assert( leaderboardEntry.m_cDetails == 3 );
// ...
// }
// once you've accessed all the entries, the data will be free'd, and the SteamLeaderboardEntries_t handle will become invalid
bool Steam_User_Stats : : GetDownloadedLeaderboardEntry ( SteamLeaderboardEntries_t hSteamLeaderboardEntries , int index , LeaderboardEntry_t * pLeaderboardEntry , int32 * pDetails , int cDetailsMax )
{
PRINT_DEBUG ( " [%i] (%i) %llu %p %p " , index , cDetailsMax , hSteamLeaderboardEntries , pLeaderboardEntry , pDetails ) ;
std : : lock_guard < std : : recursive_mutex > lock ( global_mutex ) ;
if ( hSteamLeaderboardEntries > cached_leaderboards . size ( ) | | hSteamLeaderboardEntries < = 0 ) return false ;
const auto & board = cached_leaderboards [ static_cast < unsigned > ( hSteamLeaderboardEntries - 1 ) ] ;
if ( index < 0 | | static_cast < size_t > ( index ) > = board . entries . size ( ) ) return false ;
const auto & target_entry = board . entries [ index ] ;
if ( pLeaderboardEntry ) {
LeaderboardEntry_t entry { } ;
entry . m_steamIDUser = target_entry . steam_id ;
entry . m_nGlobalRank = 1 + ( int ) ( & target_entry - & board . entries [ 0 ] ) ;
entry . m_nScore = target_entry . score ;
* pLeaderboardEntry = entry ;
}
if ( pDetails & & cDetailsMax > 0 ) {
for ( unsigned i = 0 ; i < target_entry . score_details . size ( ) & & i < static_cast < unsigned > ( cDetailsMax ) ; + + i ) {
pDetails [ i ] = target_entry . score_details [ i ] ;
}
}
return true ;
}
// Uploads a user score to the Steam back-end.
// This call is asynchronous, with the result returned in LeaderboardScoreUploaded_t
// Details are extra game-defined information regarding how the user got that score
// pScoreDetails points to an array of int32's, cScoreDetailsCount is the number of int32's in the list
STEAM_CALL_RESULT ( LeaderboardScoreUploaded_t )
SteamAPICall_t Steam_User_Stats : : UploadLeaderboardScore ( SteamLeaderboard_t hSteamLeaderboard , ELeaderboardUploadScoreMethod eLeaderboardUploadScoreMethod , int32 nScore , const int32 * pScoreDetails , int cScoreDetailsCount )
{
PRINT_DEBUG ( " %llu %i " , hSteamLeaderboard , nScore ) ;
std : : lock_guard < std : : recursive_mutex > lock ( global_mutex ) ;
if ( hSteamLeaderboard > cached_leaderboards . size ( ) | | hSteamLeaderboard < = 0 ) return k_uAPICallInvalid ; //TODO: might return callresult even if hSteamLeaderboard is invalid
auto & board = cached_leaderboards [ static_cast < unsigned > ( hSteamLeaderboard - 1 ) ] ;
auto my_entry = board . find_recent_entry ( settings - > get_local_steam_id ( ) ) ;
int current_rank = my_entry
? 1 + ( int ) ( my_entry - & board . entries [ 0 ] )
: 0 ;
int new_rank = current_rank ;
bool score_updated = false ;
if ( my_entry ) {
switch ( eLeaderboardUploadScoreMethod )
{
case k_ELeaderboardUploadScoreMethodKeepBest : { // keep user's best score
if ( board . sort_method = = k_ELeaderboardSortMethodAscending ) { // keep user's lowest score
score_updated = nScore < my_entry - > score ;
} else { // keep user's highest score
score_updated = nScore > my_entry - > score ;
}
}
break ;
case k_ELeaderboardUploadScoreMethodForceUpdate : { // always replace score with specified
score_updated = my_entry - > score ! = nScore ;
}
break ;
default : break ;
}
} else { // no entry yet for us
score_updated = true ;
}
if ( score_updated | | ( eLeaderboardUploadScoreMethod = = k_ELeaderboardUploadScoreMethodForceUpdate ) ) {
Steam_Leaderboard_Entry new_entry { } ;
new_entry . steam_id = settings - > get_local_steam_id ( ) ;
new_entry . score = nScore ;
if ( pScoreDetails & & cScoreDetailsCount > 0 ) {
for ( int i = 0 ; i < cScoreDetailsCount ; + + i ) {
new_entry . score_details . push_back ( pScoreDetails [ i ] ) ;
}
}
update_leaderboard_entry ( board , new_entry ) ;
new_rank = 1 + ( int ) ( board . find_recent_entry ( settings - > get_local_steam_id ( ) ) - & board . entries [ 0 ] ) ;
// check again in case this was a forced update
// avoid disk write if score is the same
if ( score_updated ) save_my_leaderboard_entry ( board ) ;
send_my_leaderboard_score ( board ) ;
}
LeaderboardScoreUploaded_t data { } ;
data . m_bSuccess = 1 ; //needs to be success or DOA6 freezes when uploading score.
data . m_hSteamLeaderboard = hSteamLeaderboard ;
data . m_nScore = nScore ;
data . m_bScoreChanged = score_updated ;
data . m_nGlobalRankNew = new_rank ;
data . m_nGlobalRankPrevious = current_rank ;
auto ret = callback_results - > addCallResult ( data . k_iCallback , & data , sizeof ( data ) , 0.1 ) ; // TODO is this timing ok?
callbacks - > addCBResult ( data . k_iCallback , & data , sizeof ( data ) , 0.1 ) ;
return ret ;
}
SteamAPICall_t Steam_User_Stats : : UploadLeaderboardScore ( SteamLeaderboard_t hSteamLeaderboard , int32 nScore , int32 * pScoreDetails , int cScoreDetailsCount )
{
PRINT_DEBUG ( " old " ) ;
return UploadLeaderboardScore ( hSteamLeaderboard , k_ELeaderboardUploadScoreMethodKeepBest , nScore , pScoreDetails , cScoreDetailsCount ) ;
}
// Attaches a piece of user generated content the user's entry on a leaderboard.
// hContent is a handle to a piece of user generated content that was shared using ISteamUserRemoteStorage::FileShare().
// This call is asynchronous, with the result returned in LeaderboardUGCSet_t.
STEAM_CALL_RESULT ( LeaderboardUGCSet_t )
SteamAPICall_t Steam_User_Stats : : AttachLeaderboardUGC ( SteamLeaderboard_t hSteamLeaderboard , UGCHandle_t hUGC )
{
PRINT_DEBUG_TODO ( ) ;
std : : lock_guard < std : : recursive_mutex > lock ( global_mutex ) ;
LeaderboardUGCSet_t data { } ;
if ( hSteamLeaderboard > cached_leaderboards . size ( ) | | hSteamLeaderboard < = 0 ) {
data . m_eResult = k_EResultFail ;
} else {
data . m_eResult = k_EResultOK ;
}
data . m_hSteamLeaderboard = hSteamLeaderboard ;
auto ret = callback_results - > addCallResult ( data . k_iCallback , & data , sizeof ( data ) ) ;
callbacks - > addCBResult ( data . k_iCallback , & data , sizeof ( data ) ) ;
return ret ;
}
// --- networking callbacks
// only triggered when we have a message
// someone updated their score
void Steam_User_Stats : : network_leaderboard_update_score ( Common_Message * msg , Steam_Leaderboard & board , bool send_score_back )
{
CSteamID sender_steamid ( ( uint64 ) msg - > source_id ( ) ) ;
PRINT_DEBUG ( " got score for user %llu on leaderboard '%s' (send our score back=%i) " ,
( uint64 ) msg - > source_id ( ) , board . name . c_str ( ) , ( int ) send_score_back
) ;
// when players initally load a board, and they don't have an entry in it,
// they send this msg but without their user score entry
if ( msg - > leaderboards_messages ( ) . has_user_score_entry ( ) ) {
const auto & user_score_msg = msg - > leaderboards_messages ( ) . user_score_entry ( ) ;
Steam_Leaderboard_Entry updated_entry { } ;
updated_entry . steam_id = sender_steamid ;
updated_entry . score = user_score_msg . score ( ) ;
updated_entry . score_details . reserve ( user_score_msg . score_details ( ) . size ( ) ) ;
updated_entry . score_details . assign ( user_score_msg . score_details ( ) . begin ( ) , user_score_msg . score_details ( ) . end ( ) ) ;
update_leaderboard_entry ( board , updated_entry ) ;
}
// if the sender wants back our score, send it to all, not just them
// in case we have 3 or more players and none of them have our data
if ( send_score_back ) send_my_leaderboard_score ( board ) ;
}
// someone is requesting our score on a leaderboard
void Steam_User_Stats : : network_leaderboard_send_my_score ( Common_Message * msg , const Steam_Leaderboard & board )
{
CSteamID sender_steamid ( ( uint64 ) msg - > source_id ( ) ) ;
PRINT_DEBUG ( " user %llu requested our score for leaderboard '%s' " , ( uint64 ) msg - > source_id ( ) , board . name . c_str ( ) ) ;
send_my_leaderboard_score ( board , & sender_steamid ) ;
}
void Steam_User_Stats : : network_callback_leaderboards ( Common_Message * msg )
{
// network->sendToAll() sends to current user also
if ( msg - > source_id ( ) = = settings - > get_local_steam_id ( ) . ConvertToUint64 ( ) ) return ;
if ( settings - > get_local_game_id ( ) . AppID ( ) ! = msg - > leaderboards_messages ( ) . appid ( ) ) return ;
if ( ! msg - > leaderboards_messages ( ) . has_leaderboard_info ( ) ) {
PRINT_DEBUG ( " error empty leaderboard msg " ) ;
return ;
}
const auto & board_info_msg = msg - > leaderboards_messages ( ) . leaderboard_info ( ) ;
PRINT_DEBUG ( " attempting to cache leaderboard '%s' " , board_info_msg . board_name ( ) . c_str ( ) ) ;
unsigned int board_handle = cache_leaderboard_ifneeded (
board_info_msg . board_name ( ) ,
( ELeaderboardSortMethod ) board_info_msg . sort_method ( ) ,
( ELeaderboardDisplayType ) board_info_msg . display_type ( )
) ;
switch ( msg - > leaderboards_messages ( ) . type ( ) ) {
// someone updated their score
case Leaderboards_Messages : : UpdateUserScore :
network_leaderboard_update_score ( msg , cached_leaderboards [ board_handle - 1 ] , false ) ;
break ;
// someone updated their score and wants us to share back ours
case Leaderboards_Messages : : UpdateUserScoreMutual :
network_leaderboard_update_score ( msg , cached_leaderboards [ board_handle - 1 ] , true ) ;
break ;
// someone is requesting our score on a leaderboard
case Leaderboards_Messages : : RequestUserScore :
network_leaderboard_send_my_score ( msg , cached_leaderboards [ board_handle - 1 ] ) ;
break ;
default :
PRINT_DEBUG ( " unhandled type %i " , ( int ) msg - > leaderboards_messages ( ) . type ( ) ) ;
break ;
}
}