/* 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 . */ #include "dll/steam_user_stats.h" #include // --- 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(&*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_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 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 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 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(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{}; new_board.name = name; 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(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 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 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 lock(global_mutex); if (hSteamLeaderboard > cached_leaderboards.size() || hSteamLeaderboard <= 0) return ""; return cached_leaderboards[static_cast(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 lock(global_mutex); if (hSteamLeaderboard > cached_leaderboards.size() || hSteamLeaderboard <= 0) return 0; return (int)cached_leaderboards[static_cast(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 lock(global_mutex); if (hSteamLeaderboard > cached_leaderboards.size() || hSteamLeaderboard <= 0) return k_ELeaderboardSortMethodNone; return cached_leaderboards[static_cast(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 lock(global_mutex); if (hSteamLeaderboard > cached_leaderboards.size() || hSteamLeaderboard <= 0) return k_ELeaderboardDisplayTypeNone; return cached_leaderboards[static_cast(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 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(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 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(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 lock(global_mutex); if (hSteamLeaderboardEntries > cached_leaderboards.size() || hSteamLeaderboardEntries <= 0) return false; const auto &board = cached_leaderboards[static_cast(hSteamLeaderboardEntries - 1)]; if (index < 0 || static_cast(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(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 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(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 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; } }