diff --git a/dll/steam_user_stats.cpp b/dll/steam_user_stats.cpp new file mode 100644 index 0000000..95138d6 --- /dev/null +++ b/dll/steam_user_stats.cpp @@ -0,0 +1,827 @@ +/* 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 "steam_user_stats.h" +#include "dll.h" + +unsigned int Steam_User_Stats::find_leaderboard(std::string name) +{ + unsigned index = 1; + for (auto& leaderboard : leaderboards) { + if (leaderboard.name == name) return index; + ++index; + } + + return 0; +} + +void Steam_User_Stats::load_achievements_db() +{ + std::string file_path = Local_Storage::get_game_settings_path() + achievements_user_file; + local_storage->load_json(file_path, defined_achievements); +} + +void Steam_User_Stats::load_achievements() +{ + local_storage->load_json_file("", achievements_user_file, user_achievements); +} + +void Steam_User_Stats::save_achievements() +{ + local_storage->write_json_file("", achievements_user_file, user_achievements); +} + +Steam_User_Stats::Steam_User_Stats(Settings* settings, Local_Storage* local_storage, class SteamCallResults* callback_results, class SteamCallBacks* callbacks) : + settings(settings), + local_storage(local_storage), + callback_results(callback_results), + callbacks(callbacks), + defined_achievements(nlohmann::json::object()), + user_achievements(nlohmann::json::object()) +{ + load_achievements_db(); // achievements db + load_achievements(); // achievements per user +} + +nlohmann::json const& Steam_User_Stats::GetAchievementsDb() const +{ + return defined_achievements; +} + +// Ask the server to send down this user's data and achievements for this game +STEAM_CALL_BACK(UserStatsReceived_t) +bool Steam_User_Stats::RequestCurrentStats() +{ + PRINT_DEBUG("Steam_User_Stats::RequestCurrentStats\n"); + std::lock_guard lock(global_mutex); + + UserStatsReceived_t data; + data.m_nGameID = settings->get_local_game_id().ToUint64(); + data.m_eResult = k_EResultOK; + data.m_steamIDUser = settings->get_local_steam_id(); + callbacks->addCBResult(data.k_iCallback, &data, sizeof(data), 0.1); + return true; +} + + +// Data accessors +bool Steam_User_Stats::GetStat(const char* pchName, int32* pData) +{ + PRINT_DEBUG("GetStat int32 %s\n", pchName); + if (!pchName || !pData) return false; + std::lock_guard lock(global_mutex); + auto stats_config = settings->getStats(); + auto stats_data = stats_config.find(pchName); + if (stats_data != stats_config.end()) { + if (stats_data->second.type != Stat_Type::STAT_TYPE_INT) return false; + } + + int read_data = local_storage->get_data(Local_Storage::stats_storage_folder, pchName, (char*)pData, sizeof(*pData)); + if (read_data == sizeof(int32)) + return true; + + if (stats_data != stats_config.end()) { + *pData = stats_data->second.default_value_int; + return true; + } + + return false; +} + +bool Steam_User_Stats::GetStat(const char* pchName, float* pData) +{ + PRINT_DEBUG("GetStat float %s\n", pchName); + if (!pchName || !pData) return false; + std::lock_guard lock(global_mutex); + auto stats_config = settings->getStats(); + auto stats_data = stats_config.find(pchName); + if (stats_data != stats_config.end()) { + if (stats_data->second.type == Stat_Type::STAT_TYPE_INT) return false; + } + + int read_data = local_storage->get_data(Local_Storage::stats_storage_folder, pchName, (char*)pData, sizeof(*pData)); + if (read_data == sizeof(float)) + return true; + + if (stats_data != stats_config.end()) { + *pData = stats_data->second.default_value_float; + return true; + } + + return false; +} + + +// Set / update data +bool Steam_User_Stats::SetStat(const char* pchName, int32 nData) +{ + PRINT_DEBUG("SetStat int32 %s\n", pchName); + if (!pchName) return false; + std::lock_guard lock(global_mutex); + + return local_storage->store_data(Local_Storage::stats_storage_folder, pchName, (char*)&nData, sizeof(nData)) == sizeof(nData); +} + +bool Steam_User_Stats::SetStat(const char* pchName, float fData) +{ + PRINT_DEBUG("SetStat float %s\n", pchName); + if (!pchName) return false; + std::lock_guard lock(global_mutex); + + return local_storage->store_data(Local_Storage::stats_storage_folder, pchName, (char*)&fData, sizeof(fData)) == sizeof(fData); +} + +bool Steam_User_Stats::UpdateAvgRateStat(const char* pchName, float flCountThisSession, double dSessionLength) +{ + PRINT_DEBUG("UpdateAvgRateStat %s\n", pchName); + std::lock_guard lock(global_mutex); + + char data[sizeof(float) + sizeof(float) + sizeof(double)]; + int read_data = local_storage->get_data(Local_Storage::stats_storage_folder, pchName, (char*)data, sizeof(*data)); + float oldcount = 0; + double oldsessionlength = 0; + if (read_data == sizeof(data)) { + memcpy(&oldcount, data + sizeof(float), sizeof(oldcount)); + memcpy(&oldsessionlength, data + sizeof(float) + sizeof(double), sizeof(oldsessionlength)); + } + + oldcount += flCountThisSession; + oldsessionlength += dSessionLength; + + float average = oldcount / oldsessionlength; + memcpy(data, &average, sizeof(average)); + memcpy(data + sizeof(float), &oldcount, sizeof(oldcount)); + memcpy(data + sizeof(float) * 2, &oldsessionlength, sizeof(oldsessionlength)); + + return local_storage->store_data(Local_Storage::stats_storage_folder, pchName, data, sizeof(data)) == sizeof(data); +} + + +// Achievement flag accessors +bool Steam_User_Stats::GetAchievement(const char* pchName, bool* pbAchieved) +{ + PRINT_DEBUG("GetAchievement %s\n", pchName); + if (pchName == nullptr) return false; + std::lock_guard lock(global_mutex); + + try { + auto it = std::find_if(defined_achievements.begin(), defined_achievements.end(), [pchName](nlohmann::json& item) { + return item["name"].get() == pchName; + }); + auto ach = user_achievements.find(pchName); + if (it != defined_achievements.end() && ach != user_achievements.end()) { + if (pbAchieved != nullptr) *pbAchieved = (*ach)["earned"]; + return true; + } + } + catch (...) {} + + if (pbAchieved != nullptr)*pbAchieved = false; + + return false; +} + +bool Steam_User_Stats::SetAchievement(const char* pchName) +{ + PRINT_DEBUG("SetAchievement %s\n", pchName); + if (pchName == nullptr) return false; + + try { + auto it = std::find_if(defined_achievements.begin(), defined_achievements.end(), [pchName](nlohmann::json& item) { + return item["name"].get() == pchName; + }); + if (it != defined_achievements.end()) { + if (user_achievements.find(pchName) == user_achievements.end() || user_achievements[pchName].value("earned", false) == false) { + user_achievements[pchName]["earned"] = true; + user_achievements[pchName]["earned_time"] = std::chrono::duration_cast>(std::chrono::system_clock::now().time_since_epoch()).count(); + get_steam_client()->steam_overlay->AddAchievementNotification(it.value()); + } + return true; + } + } + catch (...) {} + + return false; +} + +bool Steam_User_Stats::ClearAchievement(const char* pchName) +{ + PRINT_DEBUG("ClearAchievement %s\n", pchName); + if (pchName == nullptr) return false; + std::lock_guard lock(global_mutex); + + try { + auto it = std::find_if(defined_achievements.begin(), defined_achievements.end(), [pchName](nlohmann::json& item) { + return static_cast(item["name"]) == pchName; + }); + if (it != defined_achievements.end()) { + user_achievements[pchName]["earned"] = false; + user_achievements[pchName]["earned_time"] = static_cast(0); + return true; + } + } + catch (...) {} + + return false; +} + + +// Get the achievement status, and the time it was unlocked if unlocked. +// If the return value is true, but the unlock time is zero, that means it was unlocked before Steam +// began tracking achievement unlock times (December 2009). Time is seconds since January 1, 1970. +bool Steam_User_Stats::GetAchievementAndUnlockTime(const char* pchName, bool* pbAchieved, uint32* punUnlockTime) +{ + PRINT_DEBUG("GetAchievementAndUnlockTime\n"); + if (pchName == nullptr) return false; + std::lock_guard lock(global_mutex); + + try { + auto it = std::find_if(defined_achievements.begin(), defined_achievements.end(), [pchName](nlohmann::json& item) { + return static_cast(item["name"]) == pchName; + }); + auto ach = user_achievements.find(pchName); + if (it != defined_achievements.end() && ach != user_achievements.end()) { + if (pbAchieved != nullptr) *pbAchieved = (*ach)["earned"]; + if (punUnlockTime != nullptr) *punUnlockTime = (*ach)["earned_time"]; + return true; + } + } + catch (...) {} + + if (pbAchieved != nullptr) *pbAchieved = false; + if (punUnlockTime != nullptr) *punUnlockTime = 0; + return true; +} + + +// Store the current data on the server, will get a callback when set +// And one callback for every new achievement +// +// If the callback has a result of k_EResultInvalidParam, one or more stats +// uploaded has been rejected, either because they broke constraints +// or were out of date. In this case the server sends back updated values. +// The stats should be re-iterated to keep in sync. +bool Steam_User_Stats::StoreStats() +{ + PRINT_DEBUG("StoreStats\n"); + std::lock_guard lock(global_mutex); + + save_achievements(); + + UserStatsStored_t data; + data.m_nGameID = settings->get_local_game_id().ToUint64(); + data.m_eResult = k_EResultOK; + callbacks->addCBResult(data.k_iCallback, &data, sizeof(data)); + return true; +} + + +// Achievement / GroupAchievement metadata + +// Gets the icon of the achievement, which is a handle to be used in ISteamUtils::GetImageRGBA(), or 0 if none set. +// A return value of 0 may indicate we are still fetching data, and you can wait for the UserAchievementIconFetched_t callback +// which will notify you when the bits are ready. If the callback still returns zero, then there is no image set for the +// specified achievement. +int Steam_User_Stats::GetAchievementIcon(const char* pchName) +{ + PRINT_DEBUG("GetAchievementIcon\n"); + if (pchName == nullptr) return 0; + std::lock_guard lock(global_mutex); + + return 0; +} + + +// Get general attributes for an achievement. Accepts the following keys: +// - "name" and "desc" for retrieving the localized achievement name and description (returned in UTF8) +// - "hidden" for retrieving if an achievement is hidden (returns "0" when not hidden, "1" when hidden) +const char* Steam_User_Stats::GetAchievementDisplayAttribute(const char* pchName, const char* pchKey) +{ + PRINT_DEBUG("GetAchievementDisplayAttribute %s %s\n", pchName, pchKey); + if (pchName == nullptr) return ""; + if (pchKey == nullptr) return ""; + + std::lock_guard lock(global_mutex); + + if (strcmp(pchKey, "name") == 0) { + try { + auto it = std::find_if(defined_achievements.begin(), defined_achievements.end(), [pchName](nlohmann::json& item) { + return static_cast(item["name"]) == pchName; + }); + if (it != defined_achievements.end()) { + return it.value()["displayName"].get_ptr()->c_str(); + } + } + catch (...) {} + } + + if (strcmp(pchKey, "desc") == 0) { + try { + auto it = std::find_if(defined_achievements.begin(), defined_achievements.end(), [pchName](nlohmann::json& item) { + return static_cast(item["name"]) == pchName; + }); + if (it != defined_achievements.end()) { + return it.value()["description"].get_ptr()->c_str(); + } + } + catch (...) {} + } + + if (strcmp(pchKey, "hidden") == 0) { + try { + auto it = std::find_if(defined_achievements.begin(), defined_achievements.end(), [pchName](nlohmann::json& item) { + return static_cast(item["name"]) == pchName; + }); + if (it != defined_achievements.end()) { + return it.value()["hidden"].get_ptr()->c_str(); + } + } + catch (...) {} + } + + return ""; +} + + +// Achievement progress - triggers an AchievementProgress callback, that is all. +// Calling this w/ N out of N progress will NOT set the achievement, the game must still do that. +bool Steam_User_Stats::IndicateAchievementProgress(const char* pchName, uint32 nCurProgress, uint32 nMaxProgress) +{ + PRINT_DEBUG("IndicateAchievementProgress\n"); + if (pchName == nullptr) return false; + std::lock_guard lock(global_mutex); + + try { + auto it = std::find_if(defined_achievements.begin(), defined_achievements.end(), [pchName](nlohmann::json& item) { + return static_cast(item["name"]) == pchName; + }); + auto ach = user_achievements.find(pchName); + if (it != defined_achievements.end()) { + bool achieved = false; + if (ach != user_achievements.end()) { + bool achieved = ach->value("earned", false); + } + + UserAchievementStored_t data = {}; + data.m_nGameID = settings->get_local_game_id().ToUint64(); + data.m_bGroupAchievement = false; + strncpy(data.m_rgchAchievementName, pchName, k_cchStatNameMax); + + if (achieved) { + data.m_nCurProgress = 0; + data.m_nMaxProgress = 0; + } + else { + user_achievements[pchName]["progress"] = nCurProgress; + user_achievements[pchName]["max_progress"] = nMaxProgress; + data.m_nCurProgress = nCurProgress; + data.m_nMaxProgress = nMaxProgress; + } + + save_achievements(); + callback_results->addCallResult(data.k_iCallback, &data, sizeof(data)); + return true; + } + } + catch (...) {} + + return false; +} + + +// Used for iterating achievements. In general games should not need these functions because they should have a +// list of existing achievements compiled into them +uint32 Steam_User_Stats::GetNumAchievements() +{ + PRINT_DEBUG("GetNumAchievements\n"); + std::lock_guard lock(global_mutex); + return defined_achievements.size(); +} + +// Get achievement name iAchievement in [0,GetNumAchievements) +const char* Steam_User_Stats::GetAchievementName(uint32 iAchievement) +{ + PRINT_DEBUG("GetAchievementName\n"); + try { + static std::string achievement_name; + achievement_name = defined_achievements[iAchievement]["name"].get(); + return achievement_name.c_str(); + } + catch (...) {} + return ""; +} + + +// Friends stats & achievements + +// downloads stats for the user +// returns a UserStatsReceived_t received when completed +// if the other user has no stats, UserStatsReceived_t.m_eResult will be set to k_EResultFail +// these stats won't be auto-updated; you'll need to call RequestUserStats() again to refresh any data +STEAM_CALL_RESULT(UserStatsReceived_t) +SteamAPICall_t Steam_User_Stats::RequestUserStats(CSteamID steamIDUser) +{ + PRINT_DEBUG("Steam_User_Stats::RequestUserStats %llu\n", steamIDUser.ConvertToUint64()); + std::lock_guard lock(global_mutex); + + // Enable this to allow hot reload achievements status + //if (steamIDUser == settings->get_local_steam_id()) { + // load_achievements(); + //} + + + UserStatsReceived_t data; + data.m_nGameID = settings->get_local_game_id().ToUint64(); + data.m_eResult = k_EResultOK; + data.m_steamIDUser = steamIDUser; + return callback_results->addCallResult(data.k_iCallback, &data, sizeof(data), 0.1); +} + + +// requests stat information for a user, usable after a successful call to RequestUserStats() +bool Steam_User_Stats::GetUserStat(CSteamID steamIDUser, const char* pchName, int32* pData) +{ + PRINT_DEBUG("GetUserStat %s %llu\n", pchName, steamIDUser.ConvertToUint64()); + if (pchName == nullptr) return false; + + std::lock_guard lock(global_mutex); + + if (steamIDUser == settings->get_local_steam_id()) { + GetStat(pchName, pData); + } + else { + *pData = 0; + } + + return true; +} + +bool Steam_User_Stats::GetUserStat(CSteamID steamIDUser, const char* pchName, float* pData) +{ + PRINT_DEBUG("GetUserStat %s %llu\n", pchName, steamIDUser.ConvertToUint64()); + if (pchName == nullptr) return false; + + std::lock_guard lock(global_mutex); + + if (steamIDUser == settings->get_local_steam_id()) { + GetStat(pchName, pData); + } + else { + *pData = 0; + } + + return true; +} + +bool Steam_User_Stats::GetUserAchievement(CSteamID steamIDUser, const char* pchName, bool* pbAchieved) +{ + PRINT_DEBUG("GetUserAchievement %s\n", pchName); + if (pchName == nullptr) return false; + std::lock_guard lock(global_mutex); + + if (steamIDUser == settings->get_local_steam_id()) { + return GetAchievement(pchName, pbAchieved); + } + + return false; +} + +// See notes for GetAchievementAndUnlockTime above +bool Steam_User_Stats::GetUserAchievementAndUnlockTime(CSteamID steamIDUser, const char* pchName, bool* pbAchieved, uint32* punUnlockTime) +{ + PRINT_DEBUG("GetUserAchievementAndUnlockTime %s\n", pchName); + if (pchName == nullptr) return false; + std::lock_guard lock(global_mutex); + + if (steamIDUser == settings->get_local_steam_id()) { + return GetAchievementAndUnlockTime(pchName, pbAchieved, punUnlockTime); + } + return false; +} + + +// Reset stats +bool Steam_User_Stats::ResetAllStats(bool bAchievementsToo) +{ + PRINT_DEBUG("ResetAllStats\n"); + std::lock_guard lock(global_mutex); + //TODO + if (bAchievementsToo) { + std::for_each(user_achievements.begin(), user_achievements.end(), [](nlohmann::json& v) { + v["earned"] = false; + v["earned_time"] = 0; + }); + } + + return true; +} + + +// 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("FindOrCreateLeaderboard %s\n", pchLeaderboardName); + std::lock_guard lock(global_mutex); + + unsigned int leader = find_leaderboard(pchLeaderboardName); + if (!leader) { + struct Steam_Leaderboard leaderboard; + leaderboard.name = std::string(pchLeaderboardName); + leaderboard.sort_method = eLeaderboardSortMethod; + leaderboard.display_type = eLeaderboardDisplayType; + leaderboards.push_back(leaderboard); + leader = leaderboards.size(); + } + + LeaderboardFindResult_t data; + data.m_hSteamLeaderboard = leader; + data.m_bLeaderboardFound = 1; + return callback_results->addCallResult(data.k_iCallback, &data, sizeof(data)); +} + + +// 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("FindLeaderboard %s\n", pchLeaderboardName); + std::lock_guard lock(global_mutex); + + auto settings_Leaderboards = settings->getLeaderboards(); + if (settings_Leaderboards.count(pchLeaderboardName)) { + auto config = settings_Leaderboards[pchLeaderboardName]; + return FindOrCreateLeaderboard(pchLeaderboardName, config.sort_method, config.display_type); + } + else if (settings->createUnknownLeaderboards()) { + return FindOrCreateLeaderboard(pchLeaderboardName, k_ELeaderboardSortMethodDescending, k_ELeaderboardDisplayTypeNumeric); + } + else { + LeaderboardFindResult_t data; + data.m_hSteamLeaderboard = find_leaderboard(pchLeaderboardName);; + data.m_bLeaderboardFound = !!data.m_hSteamLeaderboard; + return callback_results->addCallResult(data.k_iCallback, &data, sizeof(data)); + } +} + + +// returns the name of a leaderboard +const char* Steam_User_Stats::GetLeaderboardName(SteamLeaderboard_t hSteamLeaderboard) +{ + PRINT_DEBUG("GetLeaderboardName\n"); + std::lock_guard lock(global_mutex); + + if (hSteamLeaderboard > leaderboards.size() || hSteamLeaderboard <= 0) return ""; + return leaderboards[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("GetLeaderboardEntryCount\n"); + return 0; +} + + +// returns the sort method of the leaderboard +ELeaderboardSortMethod Steam_User_Stats::GetLeaderboardSortMethod(SteamLeaderboard_t hSteamLeaderboard) +{ + PRINT_DEBUG("GetLeaderboardSortMethod\n"); + std::lock_guard lock(global_mutex); + if (hSteamLeaderboard > leaderboards.size() || hSteamLeaderboard <= 0) return k_ELeaderboardSortMethodNone; + return leaderboards[hSteamLeaderboard - 1].sort_method; +} + + +// returns the display type of the leaderboard +ELeaderboardDisplayType Steam_User_Stats::GetLeaderboardDisplayType(SteamLeaderboard_t hSteamLeaderboard) +{ + PRINT_DEBUG("GetLeaderboardDisplayType\n"); + std::lock_guard lock(global_mutex); + if (hSteamLeaderboard > leaderboards.size() || hSteamLeaderboard <= 0) return k_ELeaderboardDisplayTypeNone; + return leaderboards[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("DownloadLeaderboardEntries %llu %i %i %i\n", hSteamLeaderboard, eLeaderboardDataRequest, nRangeStart, nRangeEnd); + std::lock_guard lock(global_mutex); + LeaderboardScoresDownloaded_t data; + data.m_hSteamLeaderboard = hSteamLeaderboard; + data.m_hSteamLeaderboardEntries = 123; + data.m_cEntryCount = 0; + return callback_results->addCallResult(data.k_iCallback, &data, sizeof(data)); +} + +// 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("DownloadLeaderboardEntriesForUsers %i %llu\n", cUsers, cUsers > 0 ? prgUsers[0].ConvertToUint64() : 0); + std::lock_guard lock(global_mutex); + LeaderboardScoresDownloaded_t data; + data.m_hSteamLeaderboard = hSteamLeaderboard; + data.m_hSteamLeaderboardEntries = 123; + data.m_cEntryCount = 0; + return callback_results->addCallResult(data.k_iCallback, &data, sizeof(data)); +} + + +// 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("GetDownloadedLeaderboardEntry\n"); + return false; +} + + +// 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("UploadLeaderboardScore\n"); + std::lock_guard lock(global_mutex); + LeaderboardScoreUploaded_t data; + data.m_bSuccess = 1; //needs to be success or DOA6 freezes when uploading score. + //data.m_bSuccess = 0; + data.m_hSteamLeaderboard = hSteamLeaderboard; + data.m_nScore = nScore; + //data.m_bScoreChanged = 1; + data.m_bScoreChanged = 0; + //data.m_nGlobalRankNew = 1; + data.m_nGlobalRankNew = 0; + data.m_nGlobalRankPrevious = 0; + return callback_results->addCallResult(data.k_iCallback, &data, sizeof(data)); +} + +SteamAPICall_t Steam_User_Stats::UploadLeaderboardScore(SteamLeaderboard_t hSteamLeaderboard, int32 nScore, int32 * pScoreDetails, int cScoreDetailsCount) +{ + PRINT_DEBUG("UploadLeaderboardScore old\n"); + 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("AttachLeaderboardUGC\n"); + std::lock_guard lock(global_mutex); + LeaderboardUGCSet_t data = {}; + if (hSteamLeaderboard > leaderboards.size() || hSteamLeaderboard <= 0) { + data.m_eResult = k_EResultFail; + } + else { + data.m_eResult = k_EResultOK; + } + + data.m_hSteamLeaderboard = hSteamLeaderboard; + return callback_results->addCallResult(data.k_iCallback, &data, sizeof(data)); +} + + +// Retrieves the number of players currently playing your game (online + offline) +// This call is asynchronous, with the result returned in NumberOfCurrentPlayers_t +STEAM_CALL_RESULT(NumberOfCurrentPlayers_t) +SteamAPICall_t Steam_User_Stats::GetNumberOfCurrentPlayers() +{ + PRINT_DEBUG("GetNumberOfCurrentPlayers\n"); + std::lock_guard lock(global_mutex); + NumberOfCurrentPlayers_t data; + data.m_bSuccess = 1; + data.m_cPlayers = 69; + return callback_results->addCallResult(data.k_iCallback, &data, sizeof(data)); +} + + +// Requests that Steam fetch data on the percentage of players who have received each achievement +// for the game globally. +// This call is asynchronous, with the result returned in GlobalAchievementPercentagesReady_t. +STEAM_CALL_RESULT(GlobalAchievementPercentagesReady_t) +SteamAPICall_t Steam_User_Stats::RequestGlobalAchievementPercentages() +{ + PRINT_DEBUG("RequestGlobalAchievementPercentages\n"); +} + + +// Get the info on the most achieved achievement for the game, returns an iterator index you can use to fetch +// the next most achieved afterwards. Will return -1 if there is no data on achievement +// percentages (ie, you haven't called RequestGlobalAchievementPercentages and waited on the callback). +int Steam_User_Stats::GetMostAchievedAchievementInfo(char* pchName, uint32 unNameBufLen, float* pflPercent, bool* pbAchieved) +{ + PRINT_DEBUG("GetMostAchievedAchievementInfo\n"); +} + + +// Get the info on the next most achieved achievement for the game. Call this after GetMostAchievedAchievementInfo or another +// GetNextMostAchievedAchievementInfo call passing the iterator from the previous call. Returns -1 after the last +// achievement has been iterated. +int Steam_User_Stats::GetNextMostAchievedAchievementInfo(int iIteratorPrevious, char* pchName, uint32 unNameBufLen, float* pflPercent, bool* pbAchieved) +{ + PRINT_DEBUG("GetNextMostAchievedAchievementInfo\n"); +} + + +// Returns the percentage of users who have achieved the specified achievement. +bool Steam_User_Stats::GetAchievementAchievedPercent(const char* pchName, float* pflPercent) +{ + PRINT_DEBUG("GetAchievementAchievedPercent\n"); +} + + +// Requests global stats data, which is available for stats marked as "aggregated". +// This call is asynchronous, with the results returned in GlobalStatsReceived_t. +// nHistoryDays specifies how many days of day-by-day history to retrieve in addition +// to the overall totals. The limit is 60. +STEAM_CALL_RESULT(GlobalStatsReceived_t) +SteamAPICall_t Steam_User_Stats::RequestGlobalStats(int nHistoryDays) +{ + PRINT_DEBUG("RequestGlobalStats %i\n", nHistoryDays); + std::lock_guard lock(global_mutex); + GlobalStatsReceived_t data; + data.m_nGameID = settings->get_local_game_id().ToUint64(); + data.m_eResult = k_EResultOK; + return callback_results->addCallResult(data.k_iCallback, &data, sizeof(data)); +} + + +// Gets the lifetime totals for an aggregated stat +bool Steam_User_Stats::GetGlobalStat(const char* pchStatName, int64 * pData) +{ + PRINT_DEBUG("GetGlobalStat %s\n", pchStatName); + return false; +} + +bool Steam_User_Stats::GetGlobalStat(const char* pchStatName, double* pData) +{ + PRINT_DEBUG("GetGlobalStat %s\n", pchStatName); + return false; +} + + +// Gets history for an aggregated stat. pData will be filled with daily values, starting with today. +// So when called, pData[0] will be today, pData[1] will be yesterday, and pData[2] will be two days ago, +// etc. cubData is the size in bytes of the pubData buffer. Returns the number of +// elements actually set. +int32 Steam_User_Stats::GetGlobalStatHistory(const char* pchStatName, STEAM_ARRAY_COUNT(cubData) int64 * pData, uint32 cubData) +{ + PRINT_DEBUG("GetGlobalStatHistory int64 %s\n", pchStatName); + return 0; +} + +int32 Steam_User_Stats::GetGlobalStatHistory(const char* pchStatName, STEAM_ARRAY_COUNT(cubData) double* pData, uint32 cubData) +{ + PRINT_DEBUG("GetGlobalStatHistory double %s\n", pchStatName); + return 0; +} diff --git a/dll/steam_user_stats.h b/dll/steam_user_stats.h index 7bc9e9e..291ab15 100644 --- a/dll/steam_user_stats.h +++ b/dll/steam_user_stats.h @@ -15,636 +15,252 @@ License along with the Goldberg Emulator; if not, see . */ +#ifndef __INCLUDED_STEAM_USER_STATS_H__ +#define __INCLUDED_STEAM_USER_STATS_H__ + #include "base.h" +#include +#include +#include "../json/json.hpp" + struct Steam_Leaderboard { std::string name; ELeaderboardSortMethod sort_method; ELeaderboardDisplayType display_type; }; - class Steam_User_Stats : -public ISteamUserStats003, -public ISteamUserStats004, -public ISteamUserStats005, -public ISteamUserStats006, -public ISteamUserStats007, -public ISteamUserStats008, -public ISteamUserStats009, -public ISteamUserStats010, -public ISteamUserStats + public ISteamUserStats003, + public ISteamUserStats004, + public ISteamUserStats005, + public ISteamUserStats006, + public ISteamUserStats007, + public ISteamUserStats008, + public ISteamUserStats009, + public ISteamUserStats010, + public ISteamUserStats { - Local_Storage *local_storage; - Settings *settings; - SteamCallResults *callback_results; - class SteamCallBacks *callbacks; +public: + static constexpr auto achievements_user_file = "achievements.json"; + +private: + + Local_Storage* local_storage; + Settings* settings; + SteamCallResults* callback_results; + class SteamCallBacks* callbacks; std::vector leaderboards; -unsigned int find_leaderboard(std::string name) -{ - unsigned index = 1; - for (auto &leaderboard : leaderboards) { - if (leaderboard.name == name) return index; - ++index; - } + nlohmann::json defined_achievements; + nlohmann::json user_achievements; - return 0; -} + unsigned int find_leaderboard(std::string name); + + void load_achievements_db(); + void load_achievements(); + void save_achievements(); public: -Steam_User_Stats(Settings *settings, Local_Storage *local_storage, class SteamCallResults *callback_results, class SteamCallBacks *callbacks) -{ - this->local_storage = local_storage; - this->settings = settings; - this->callback_results = callback_results; - this->callbacks = callbacks; -} - -// Ask the server to send down this user's data and achievements for this game -STEAM_CALL_BACK( UserStatsReceived_t ) -bool RequestCurrentStats() -{ - PRINT_DEBUG("Steam_User_Stats::RequestCurrentStats\n"); - std::lock_guard lock(global_mutex); - - UserStatsReceived_t data; - data.m_nGameID = settings->get_local_game_id().ToUint64(); - data.m_eResult = k_EResultOK; - data.m_steamIDUser = settings->get_local_steam_id(); - callbacks->addCBResult(data.k_iCallback, &data, sizeof(data), 0.1); - return true; -} - - -// Data accessors -bool GetStat( const char *pchName, int32 *pData ) -{ - PRINT_DEBUG("GetStat int32 %s\n", pchName); - if (!pchName || !pData) return false; - std::lock_guard lock(global_mutex); - auto stats_config = settings->getStats(); - auto stats_data = stats_config.find(pchName); - if (stats_data != stats_config.end()) { - if (stats_data->second.type != Stat_Type::STAT_TYPE_INT) return false; - } - - int read_data = local_storage->get_data(STATS_STORAGE_FOLDER, pchName, (char* )pData, sizeof(*pData)); - if (read_data == sizeof(int32)) - return true; - - if (stats_data != stats_config.end()) { - *pData = stats_data->second.default_value_int; - return true; - } - - return false; -} - -bool GetStat( const char *pchName, float *pData ) -{ - PRINT_DEBUG("GetStat float %s\n", pchName); - if (!pchName || !pData) return false; - std::lock_guard lock(global_mutex); - auto stats_config = settings->getStats(); - auto stats_data = stats_config.find(pchName); - if (stats_data != stats_config.end()) { - if (stats_data->second.type == Stat_Type::STAT_TYPE_INT) return false; - } - - int read_data = local_storage->get_data(STATS_STORAGE_FOLDER, pchName, (char* )pData, sizeof(*pData)); - if (read_data == sizeof(float)) - return true; - - if (stats_data != stats_config.end()) { - *pData = stats_data->second.default_value_float; - return true; - } - - return false; -} - - -// Set / update data -bool SetStat( const char *pchName, int32 nData ) -{ - PRINT_DEBUG("SetStat int32 %s\n", pchName); - if (!pchName) return false; - std::lock_guard lock(global_mutex); - - return local_storage->store_data(STATS_STORAGE_FOLDER, pchName, (char* )&nData, sizeof(nData)) == sizeof(nData); -} - -bool SetStat( const char *pchName, float fData ) -{ - PRINT_DEBUG("SetStat float %s\n", pchName); - if (!pchName) return false; - std::lock_guard lock(global_mutex); - - return local_storage->store_data(STATS_STORAGE_FOLDER, pchName, (char* )&fData, sizeof(fData)) == sizeof(fData); -} - -bool UpdateAvgRateStat( const char *pchName, float flCountThisSession, double dSessionLength ) -{ - PRINT_DEBUG("UpdateAvgRateStat %s\n", pchName); - std::lock_guard lock(global_mutex); - - char data[sizeof(float) + sizeof(float) + sizeof(double)]; - int read_data = local_storage->get_data(STATS_STORAGE_FOLDER, pchName, (char* )data, sizeof(*data)); - float oldcount = 0; - double oldsessionlength = 0; - if (read_data == sizeof(data)) { - memcpy(&oldcount, data + sizeof(float), sizeof(oldcount)); - memcpy(&oldsessionlength, data + sizeof(float) + sizeof(double), sizeof(oldsessionlength)); - } - - oldcount += flCountThisSession; - oldsessionlength += dSessionLength; - - float average = oldcount / oldsessionlength; - memcpy(data, &average, sizeof(average)); - memcpy(data + sizeof(float), &oldcount, sizeof(oldcount)); - memcpy(data + sizeof(float) * 2, &oldsessionlength, sizeof(oldsessionlength)); - - return local_storage->store_data(STATS_STORAGE_FOLDER, pchName, data, sizeof(data)) == sizeof(data); -} - - -// Achievement flag accessors -bool GetAchievement( const char *pchName, bool *pbAchieved ) -{ - //TODO: these achievement functions need to load a list of achievements from somewhere, return false so that kf2 doesn't loop endlessly - PRINT_DEBUG("GetAchievement %s\n", pchName); - *pbAchieved = false; - return false; -} - -bool SetAchievement( const char *pchName ) -{ - PRINT_DEBUG("SetAchievement %s\n", pchName); - return false; -} - -bool ClearAchievement( const char *pchName ) -{ - PRINT_DEBUG("ClearAchievement %s\n", pchName); - return false; -} - - -// Get the achievement status, and the time it was unlocked if unlocked. -// If the return value is true, but the unlock time is zero, that means it was unlocked before Steam -// began tracking achievement unlock times (December 2009). Time is seconds since January 1, 1970. -bool GetAchievementAndUnlockTime( const char *pchName, bool *pbAchieved, uint32 *punUnlockTime ) -{ - PRINT_DEBUG("GetAchievementAndUnlockTime\n"); - *pbAchieved = false; - return true; -} - - -// Store the current data on the server, will get a callback when set -// And one callback for every new achievement -// -// If the callback has a result of k_EResultInvalidParam, one or more stats -// uploaded has been rejected, either because they broke constraints -// or were out of date. In this case the server sends back updated values. -// The stats should be re-iterated to keep in sync. -bool StoreStats() -{ - PRINT_DEBUG("StoreStats\n"); - std::lock_guard lock(global_mutex); - - UserStatsStored_t data; - data.m_nGameID = settings->get_local_game_id().ToUint64(); - data.m_eResult = k_EResultOK; - callbacks->addCBResult(data.k_iCallback, &data, sizeof(data)); - return true; -} - - -// Achievement / GroupAchievement metadata - -// Gets the icon of the achievement, which is a handle to be used in ISteamUtils::GetImageRGBA(), or 0 if none set. -// A return value of 0 may indicate we are still fetching data, and you can wait for the UserAchievementIconFetched_t callback -// which will notify you when the bits are ready. If the callback still returns zero, then there is no image set for the -// specified achievement. -int GetAchievementIcon( const char *pchName ) -{ - PRINT_DEBUG("GetAchievementIcon\n"); - return 0; -} - - -// Get general attributes for an achievement. Accepts the following keys: -// - "name" and "desc" for retrieving the localized achievement name and description (returned in UTF8) -// - "hidden" for retrieving if an achievement is hidden (returns "0" when not hidden, "1" when hidden) -const char * GetAchievementDisplayAttribute( const char *pchName, const char *pchKey ) -{ - PRINT_DEBUG("GetAchievementDisplayAttribute %s %s\n", pchName, pchKey); - return ""; //TODO - - if (strcmp (pchKey, "name") == 0) { - return "Achievement Name"; - } - - if (strcmp (pchKey, "desc") == 0) { - return "Achievement Description"; - } - - if (strcmp (pchKey, "hidden") == 0) { - return "0"; - } - - return ""; -} - - -// Achievement progress - triggers an AchievementProgress callback, that is all. -// Calling this w/ N out of N progress will NOT set the achievement, the game must still do that. -bool IndicateAchievementProgress( const char *pchName, uint32 nCurProgress, uint32 nMaxProgress ) -{ - PRINT_DEBUG("IndicateAchievementProgress\n"); -} - - -// Used for iterating achievements. In general games should not need these functions because they should have a -// list of existing achievements compiled into them -uint32 GetNumAchievements() -{ - PRINT_DEBUG("GetNumAchievements\n"); - return 0; -} - -// Get achievement name iAchievement in [0,GetNumAchievements) -const char * GetAchievementName( uint32 iAchievement ) -{ - PRINT_DEBUG("GetAchievementName\n"); - return ""; -} - - -// Friends stats & achievements - -// downloads stats for the user -// returns a UserStatsReceived_t received when completed -// if the other user has no stats, UserStatsReceived_t.m_eResult will be set to k_EResultFail -// these stats won't be auto-updated; you'll need to call RequestUserStats() again to refresh any data -STEAM_CALL_RESULT( UserStatsReceived_t ) -SteamAPICall_t RequestUserStats( CSteamID steamIDUser ) -{ - PRINT_DEBUG("Steam_User_Stats::RequestUserStats %llu\n", steamIDUser.ConvertToUint64()); - std::lock_guard lock(global_mutex); - - UserStatsReceived_t data; - data.m_nGameID = settings->get_local_game_id().ToUint64(); - data.m_eResult = k_EResultOK; - data.m_steamIDUser = steamIDUser; - return callback_results->addCallResult(data.k_iCallback, &data, sizeof(data), 0.1); -} - - -// requests stat information for a user, usable after a successful call to RequestUserStats() -bool GetUserStat( CSteamID steamIDUser, const char *pchName, int32 *pData ) -{ - PRINT_DEBUG("GetUserStat %s %llu\n", pchName, steamIDUser.ConvertToUint64()); - std::lock_guard lock(global_mutex); - - if (steamIDUser == settings->get_local_steam_id()) { - GetStat(pchName, pData); - } else { - *pData = 0; - } - - return true; -} - -bool GetUserStat( CSteamID steamIDUser, const char *pchName, float *pData ) -{ - PRINT_DEBUG("GetUserStat %s %llu\n", pchName, steamIDUser.ConvertToUint64()); - std::lock_guard lock(global_mutex); - - if (steamIDUser == settings->get_local_steam_id()) { - GetStat(pchName, pData); - } else { - *pData = 0; - } - - return true; -} - -bool GetUserAchievement( CSteamID steamIDUser, const char *pchName, bool *pbAchieved ) -{ - PRINT_DEBUG("GetUserAchievement %s\n", pchName); - return false; -} - -// See notes for GetAchievementAndUnlockTime above -bool GetUserAchievementAndUnlockTime( CSteamID steamIDUser, const char *pchName, bool *pbAchieved, uint32 *punUnlockTime ) -{ - PRINT_DEBUG("GetUserAchievementAndUnlockTime %s\n", pchName); - return false; -} - - -// Reset stats -bool ResetAllStats( bool bAchievementsToo ) -{ - PRINT_DEBUG("ResetAllStats\n"); - //TODO - return true; -} - - -// 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 FindOrCreateLeaderboard( const char *pchLeaderboardName, ELeaderboardSortMethod eLeaderboardSortMethod, ELeaderboardDisplayType eLeaderboardDisplayType ) -{ - PRINT_DEBUG("FindOrCreateLeaderboard %s\n", pchLeaderboardName); - std::lock_guard lock(global_mutex); - - unsigned int leader = find_leaderboard(pchLeaderboardName); - if (!leader) { - struct Steam_Leaderboard leaderboard; - leaderboard.name = std::string(pchLeaderboardName); - leaderboard.sort_method = eLeaderboardSortMethod; - leaderboard.display_type = eLeaderboardDisplayType; - leaderboards.push_back(leaderboard); - leader = leaderboards.size(); - } - - LeaderboardFindResult_t data; - data.m_hSteamLeaderboard = leader; - data.m_bLeaderboardFound = 1; - return callback_results->addCallResult(data.k_iCallback, &data, sizeof(data)); -} - - -// 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 FindLeaderboard( const char *pchLeaderboardName ) -{ - PRINT_DEBUG("FindLeaderboard %s\n", pchLeaderboardName); - std::lock_guard lock(global_mutex); - - auto settings_Leaderboards = settings->getLeaderboards(); - if (settings_Leaderboards.count(pchLeaderboardName)) { - auto config = settings_Leaderboards[pchLeaderboardName]; - return FindOrCreateLeaderboard(pchLeaderboardName, config.sort_method, config.display_type); - } else if (settings->createUnknownLeaderboards()) { - return FindOrCreateLeaderboard(pchLeaderboardName, k_ELeaderboardSortMethodDescending, k_ELeaderboardDisplayTypeNumeric); - } else { - LeaderboardFindResult_t data; - data.m_hSteamLeaderboard = find_leaderboard(pchLeaderboardName);; - data.m_bLeaderboardFound = !!data.m_hSteamLeaderboard; - return callback_results->addCallResult(data.k_iCallback, &data, sizeof(data)); - } -} - - -// returns the name of a leaderboard -const char * GetLeaderboardName( SteamLeaderboard_t hSteamLeaderboard ) -{ - PRINT_DEBUG("GetLeaderboardName\n"); - std::lock_guard lock(global_mutex); - - if (hSteamLeaderboard > leaderboards.size() || hSteamLeaderboard <= 0) return ""; - return leaderboards[hSteamLeaderboard - 1].name.c_str(); -} - - -// returns the total number of entries in a leaderboard, as of the last request -int GetLeaderboardEntryCount( SteamLeaderboard_t hSteamLeaderboard ) -{ - PRINT_DEBUG("GetLeaderboardEntryCount\n"); - return 0; -} - - -// returns the sort method of the leaderboard -ELeaderboardSortMethod GetLeaderboardSortMethod( SteamLeaderboard_t hSteamLeaderboard ) -{ - PRINT_DEBUG("GetLeaderboardSortMethod\n"); - std::lock_guard lock(global_mutex); - if (hSteamLeaderboard > leaderboards.size() || hSteamLeaderboard <= 0) return k_ELeaderboardSortMethodNone; - return leaderboards[hSteamLeaderboard - 1].sort_method; -} - - -// returns the display type of the leaderboard -ELeaderboardDisplayType GetLeaderboardDisplayType( SteamLeaderboard_t hSteamLeaderboard ) -{ - PRINT_DEBUG("GetLeaderboardDisplayType\n"); - std::lock_guard lock(global_mutex); - if (hSteamLeaderboard > leaderboards.size() || hSteamLeaderboard <= 0) return k_ELeaderboardDisplayTypeNone; - return leaderboards[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 DownloadLeaderboardEntries( SteamLeaderboard_t hSteamLeaderboard, ELeaderboardDataRequest eLeaderboardDataRequest, int nRangeStart, int nRangeEnd ) -{ - PRINT_DEBUG("DownloadLeaderboardEntries %llu %i %i %i\n", hSteamLeaderboard, eLeaderboardDataRequest, nRangeStart, nRangeEnd); - std::lock_guard lock(global_mutex); - LeaderboardScoresDownloaded_t data; - data.m_hSteamLeaderboard = hSteamLeaderboard; - data.m_hSteamLeaderboardEntries = 123; - data.m_cEntryCount = 0; - return callback_results->addCallResult(data.k_iCallback, &data, sizeof(data)); -} - -// 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 DownloadLeaderboardEntriesForUsers( SteamLeaderboard_t hSteamLeaderboard, - STEAM_ARRAY_COUNT_D(cUsers, Array of users to retrieve) CSteamID *prgUsers, int cUsers ) -{ - PRINT_DEBUG("DownloadLeaderboardEntriesForUsers %i %llu\n", cUsers, cUsers > 0 ? prgUsers[0].ConvertToUint64() : 0); - std::lock_guard lock(global_mutex); - LeaderboardScoresDownloaded_t data; - data.m_hSteamLeaderboard = hSteamLeaderboard; - data.m_hSteamLeaderboardEntries = 123; - data.m_cEntryCount = 0; - return callback_results->addCallResult(data.k_iCallback, &data, sizeof(data)); -} - - -// 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 GetDownloadedLeaderboardEntry( SteamLeaderboardEntries_t hSteamLeaderboardEntries, int index, LeaderboardEntry_t *pLeaderboardEntry, int32 *pDetails, int cDetailsMax ) -{ - PRINT_DEBUG("GetDownloadedLeaderboardEntry\n"); - return false; -} - - -// 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 UploadLeaderboardScore( SteamLeaderboard_t hSteamLeaderboard, ELeaderboardUploadScoreMethod eLeaderboardUploadScoreMethod, int32 nScore, const int32 *pScoreDetails, int cScoreDetailsCount ) -{ - PRINT_DEBUG("UploadLeaderboardScore\n"); - std::lock_guard lock(global_mutex); - LeaderboardScoreUploaded_t data; - data.m_bSuccess = 1; //needs to be success or DOA6 freezes when uploading score. - //data.m_bSuccess = 0; - data.m_hSteamLeaderboard = hSteamLeaderboard; - data.m_nScore = nScore; - //data.m_bScoreChanged = 1; - data.m_bScoreChanged = 0; - //data.m_nGlobalRankNew = 1; - data.m_nGlobalRankNew = 0; - data.m_nGlobalRankPrevious = 0; - return callback_results->addCallResult(data.k_iCallback, &data, sizeof(data)); -} - -SteamAPICall_t UploadLeaderboardScore( SteamLeaderboard_t hSteamLeaderboard, int32 nScore, int32 *pScoreDetails, int cScoreDetailsCount ) -{ - PRINT_DEBUG("UploadLeaderboardScore old\n"); - 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 AttachLeaderboardUGC( SteamLeaderboard_t hSteamLeaderboard, UGCHandle_t hUGC ) -{ - PRINT_DEBUG("AttachLeaderboardUGC\n"); - std::lock_guard lock(global_mutex); - LeaderboardUGCSet_t data = {}; - if (hSteamLeaderboard > leaderboards.size() || hSteamLeaderboard <= 0) { - data.m_eResult = k_EResultFail; - } else { - data.m_eResult = k_EResultOK; - } - - data.m_hSteamLeaderboard = hSteamLeaderboard; - return callback_results->addCallResult(data.k_iCallback, &data, sizeof(data)); -} - - -// Retrieves the number of players currently playing your game (online + offline) -// This call is asynchronous, with the result returned in NumberOfCurrentPlayers_t -STEAM_CALL_RESULT( NumberOfCurrentPlayers_t ) -SteamAPICall_t GetNumberOfCurrentPlayers() -{ - PRINT_DEBUG("GetNumberOfCurrentPlayers\n"); - std::lock_guard lock(global_mutex); - NumberOfCurrentPlayers_t data; - data.m_bSuccess = 1; - data.m_cPlayers = 69; - return callback_results->addCallResult(data.k_iCallback, &data, sizeof(data)); -} - - -// Requests that Steam fetch data on the percentage of players who have received each achievement -// for the game globally. -// This call is asynchronous, with the result returned in GlobalAchievementPercentagesReady_t. -STEAM_CALL_RESULT( GlobalAchievementPercentagesReady_t ) -SteamAPICall_t RequestGlobalAchievementPercentages() -{ - PRINT_DEBUG("RequestGlobalAchievementPercentages\n"); -} - - -// Get the info on the most achieved achievement for the game, returns an iterator index you can use to fetch -// the next most achieved afterwards. Will return -1 if there is no data on achievement -// percentages (ie, you haven't called RequestGlobalAchievementPercentages and waited on the callback). -int GetMostAchievedAchievementInfo( char *pchName, uint32 unNameBufLen, float *pflPercent, bool *pbAchieved ) -{ - PRINT_DEBUG("GetMostAchievedAchievementInfo\n"); -} - - -// Get the info on the next most achieved achievement for the game. Call this after GetMostAchievedAchievementInfo or another -// GetNextMostAchievedAchievementInfo call passing the iterator from the previous call. Returns -1 after the last -// achievement has been iterated. -int GetNextMostAchievedAchievementInfo( int iIteratorPrevious, char *pchName, uint32 unNameBufLen, float *pflPercent, bool *pbAchieved ) -{ - PRINT_DEBUG("GetNextMostAchievedAchievementInfo\n"); -} - - -// Returns the percentage of users who have achieved the specified achievement. -bool GetAchievementAchievedPercent( const char *pchName, float *pflPercent ) -{ - PRINT_DEBUG("GetAchievementAchievedPercent\n"); -} - - -// Requests global stats data, which is available for stats marked as "aggregated". -// This call is asynchronous, with the results returned in GlobalStatsReceived_t. -// nHistoryDays specifies how many days of day-by-day history to retrieve in addition -// to the overall totals. The limit is 60. -STEAM_CALL_RESULT( GlobalStatsReceived_t ) -SteamAPICall_t RequestGlobalStats( int nHistoryDays ) -{ - PRINT_DEBUG("RequestGlobalStats %i\n", nHistoryDays); - std::lock_guard lock(global_mutex); - GlobalStatsReceived_t data; - data.m_nGameID = settings->get_local_game_id().ToUint64(); - data.m_eResult = k_EResultOK; - return callback_results->addCallResult(data.k_iCallback, &data, sizeof(data)); -} - - -// Gets the lifetime totals for an aggregated stat -bool GetGlobalStat( const char *pchStatName, int64 *pData ) -{ - PRINT_DEBUG("GetGlobalStat %s\n", pchStatName); - return false; -} - -bool GetGlobalStat( const char *pchStatName, double *pData ) -{ - PRINT_DEBUG("GetGlobalStat %s\n", pchStatName); - return false; -} - - -// Gets history for an aggregated stat. pData will be filled with daily values, starting with today. -// So when called, pData[0] will be today, pData[1] will be yesterday, and pData[2] will be two days ago, -// etc. cubData is the size in bytes of the pubData buffer. Returns the number of -// elements actually set. -int32 GetGlobalStatHistory( const char *pchStatName, STEAM_ARRAY_COUNT(cubData) int64 *pData, uint32 cubData ) -{ - PRINT_DEBUG("GetGlobalStatHistory int64 %s\n", pchStatName); - return 0; -} - -int32 GetGlobalStatHistory( const char *pchStatName, STEAM_ARRAY_COUNT(cubData) double *pData, uint32 cubData ) -{ - PRINT_DEBUG("GetGlobalStatHistory double %s\n", pchStatName); - return 0; -} + Steam_User_Stats(Settings* settings, Local_Storage* local_storage, class SteamCallResults* callback_results, class SteamCallBacks* callbacks); + + nlohmann::json const& GetAchievementsDb() const; + + // Ask the server to send down this user's data and achievements for this game + STEAM_CALL_BACK(UserStatsReceived_t) + bool RequestCurrentStats(); + + // Data accessors + bool GetStat(const char* pchName, int32* pData); + bool GetStat(const char* pchName, float* pData); + + // Set / update data + bool SetStat(const char* pchName, int32 nData); + bool SetStat(const char* pchName, float fData); + bool UpdateAvgRateStat(const char* pchName, float flCountThisSession, double dSessionLength); + + // Achievement flag accessors + bool GetAchievement(const char* pchName, bool* pbAchieved); + bool SetAchievement(const char* pchName); + bool ClearAchievement(const char* pchName); + + + // Get the achievement status, and the time it was unlocked if unlocked. + // If the return value is true, but the unlock time is zero, that means it was unlocked before Steam + // began tracking achievement unlock times (December 2009). Time is seconds since January 1, 1970. + bool GetAchievementAndUnlockTime(const char* pchName, bool* pbAchieved, uint32* punUnlockTime); + + // Store the current data on the server, will get a callback when set + // And one callback for every new achievement + // + // If the callback has a result of k_EResultInvalidParam, one or more stats + // uploaded has been rejected, either because they broke constraints + // or were out of date. In this case the server sends back updated values. + // The stats should be re-iterated to keep in sync. + bool StoreStats(); + + // Achievement / GroupAchievement metadata + + // Gets the icon of the achievement, which is a handle to be used in ISteamUtils::GetImageRGBA(), or 0 if none set. + // A return value of 0 may indicate we are still fetching data, and you can wait for the UserAchievementIconFetched_t callback + // which will notify you when the bits are ready. If the callback still returns zero, then there is no image set for the + // specified achievement. + int GetAchievementIcon(const char* pchName); + + + // Get general attributes for an achievement. Accepts the following keys: + // - "name" and "desc" for retrieving the localized achievement name and description (returned in UTF8) + // - "hidden" for retrieving if an achievement is hidden (returns "0" when not hidden, "1" when hidden) + const char* GetAchievementDisplayAttribute(const char* pchName, const char* pchKey); + + // Achievement progress - triggers an AchievementProgress callback, that is all. + // Calling this w/ N out of N progress will NOT set the achievement, the game must still do that. + bool IndicateAchievementProgress(const char* pchName, uint32 nCurProgress, uint32 nMaxProgress); + + // Used for iterating achievements. In general games should not need these functions because they should have a + // list of existing achievements compiled into them + uint32 GetNumAchievements(); + + // Get achievement name iAchievement in [0,GetNumAchievements) + const char* GetAchievementName(uint32 iAchievement); + + // Friends stats & achievements + + // downloads stats for the user + // returns a UserStatsReceived_t received when completed + // if the other user has no stats, UserStatsReceived_t.m_eResult will be set to k_EResultFail + // these stats won't be auto-updated; you'll need to call RequestUserStats() again to refresh any data + STEAM_CALL_RESULT(UserStatsReceived_t) + SteamAPICall_t RequestUserStats(CSteamID steamIDUser); + + + // requests stat information for a user, usable after a successful call to RequestUserStats() + bool GetUserStat(CSteamID steamIDUser, const char* pchName, int32* pData); + bool GetUserStat(CSteamID steamIDUser, const char* pchName, float* pData); + + bool GetUserAchievement(CSteamID steamIDUser, const char* pchName, bool* pbAchieved); + + // See notes for GetAchievementAndUnlockTime above + bool GetUserAchievementAndUnlockTime(CSteamID steamIDUser, const char* pchName, bool* pbAchieved, uint32* punUnlockTime); + + // Reset stats + bool ResetAllStats(bool bAchievementsToo); + + // 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 FindOrCreateLeaderboard(const char* pchLeaderboardName, ELeaderboardSortMethod eLeaderboardSortMethod, ELeaderboardDisplayType eLeaderboardDisplayType); + + // 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 FindLeaderboard(const char* pchLeaderboardName); + + // returns the name of a leaderboard + const char* GetLeaderboardName(SteamLeaderboard_t hSteamLeaderboard); + + // returns the total number of entries in a leaderboard, as of the last request + int GetLeaderboardEntryCount(SteamLeaderboard_t hSteamLeaderboard); + + // returns the sort method of the leaderboard + ELeaderboardSortMethod GetLeaderboardSortMethod(SteamLeaderboard_t hSteamLeaderboard); + + // returns the display type of the leaderboard + ELeaderboardDisplayType GetLeaderboardDisplayType(SteamLeaderboard_t hSteamLeaderboard); + + // 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 DownloadLeaderboardEntries(SteamLeaderboard_t hSteamLeaderboard, ELeaderboardDataRequest eLeaderboardDataRequest, int nRangeStart, int nRangeEnd); + + // 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 DownloadLeaderboardEntriesForUsers(SteamLeaderboard_t hSteamLeaderboard, + STEAM_ARRAY_COUNT_D(cUsers, Array of users to retrieve) CSteamID* prgUsers, int cUsers); + + // 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 GetDownloadedLeaderboardEntry(SteamLeaderboardEntries_t hSteamLeaderboardEntries, int index, LeaderboardEntry_t* pLeaderboardEntry, int32* pDetails, int cDetailsMax); + + // 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 UploadLeaderboardScore(SteamLeaderboard_t hSteamLeaderboard, ELeaderboardUploadScoreMethod eLeaderboardUploadScoreMethod, int32 nScore, const int32* pScoreDetails, int cScoreDetailsCount); + + SteamAPICall_t UploadLeaderboardScore(SteamLeaderboard_t hSteamLeaderboard, int32 nScore, int32* pScoreDetails, int 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 AttachLeaderboardUGC(SteamLeaderboard_t hSteamLeaderboard, UGCHandle_t hUGC); + + // Retrieves the number of players currently playing your game (online + offline) + // This call is asynchronous, with the result returned in NumberOfCurrentPlayers_t + STEAM_CALL_RESULT(NumberOfCurrentPlayers_t) + SteamAPICall_t GetNumberOfCurrentPlayers(); + + // Requests that Steam fetch data on the percentage of players who have received each achievement + // for the game globally. + // This call is asynchronous, with the result returned in GlobalAchievementPercentagesReady_t. + STEAM_CALL_RESULT(GlobalAchievementPercentagesReady_t) + SteamAPICall_t RequestGlobalAchievementPercentages(); + + // Get the info on the most achieved achievement for the game, returns an iterator index you can use to fetch + // the next most achieved afterwards. Will return -1 if there is no data on achievement + // percentages (ie, you haven't called RequestGlobalAchievementPercentages and waited on the callback). + int GetMostAchievedAchievementInfo(char* pchName, uint32 unNameBufLen, float* pflPercent, bool* pbAchieved); + + // Get the info on the next most achieved achievement for the game. Call this after GetMostAchievedAchievementInfo or another + // GetNextMostAchievedAchievementInfo call passing the iterator from the previous call. Returns -1 after the last + // achievement has been iterated. + int GetNextMostAchievedAchievementInfo(int iIteratorPrevious, char* pchName, uint32 unNameBufLen, float* pflPercent, bool* pbAchieved); + + // Returns the percentage of users who have achieved the specified achievement. + bool GetAchievementAchievedPercent(const char* pchName, float* pflPercent); + + // Requests global stats data, which is available for stats marked as "aggregated". + // This call is asynchronous, with the results returned in GlobalStatsReceived_t. + // nHistoryDays specifies how many days of day-by-day history to retrieve in addition + // to the overall totals. The limit is 60. + STEAM_CALL_RESULT(GlobalStatsReceived_t) + SteamAPICall_t RequestGlobalStats(int nHistoryDays); + + // Gets the lifetime totals for an aggregated stat + bool GetGlobalStat(const char* pchStatName, int64* pData); + bool GetGlobalStat(const char* pchStatName, double* pData); + + // Gets history for an aggregated stat. pData will be filled with daily values, starting with today. + // So when called, pData[0] will be today, pData[1] will be yesterday, and pData[2] will be two days ago, + // etc. cubData is the size in bytes of the pubData buffer. Returns the number of + // elements actually set. + int32 GetGlobalStatHistory(const char* pchStatName, STEAM_ARRAY_COUNT(cubData) int64* pData, uint32 cubData); + int32 GetGlobalStatHistory(const char* pchStatName, STEAM_ARRAY_COUNT(cubData) double* pData, uint32 cubData); }; + +#endif//__INCLUDED_STEAM_USER_STATS_H__ diff --git a/overlay_experimental/steam_overlay.cpp b/overlay_experimental/steam_overlay.cpp index d6f3e90..7cb741f 100644 --- a/overlay_experimental/steam_overlay.cpp +++ b/overlay_experimental/steam_overlay.cpp @@ -213,7 +213,7 @@ void Steam_Overlay::ShowOverlay(bool state) overlay_state_changed = true; } -void Steam_Overlay::NotifyUser(friend_window_state& friend_state, std::string const& message) +void Steam_Overlay::NotifyUser(friend_window_state& friend_state) { if (!(friend_state.window_state & window_state_show) || !show_overlay) { @@ -221,7 +221,6 @@ void Steam_Overlay::NotifyUser(friend_window_state& friend_state, std::string co #ifdef __WINDOWS__ PlaySound((LPCSTR)notif_invite_wav, NULL, SND_ASYNC | SND_MEMORY); #endif - AddNotification(message); } } @@ -239,7 +238,8 @@ void Steam_Overlay::SetLobbyInvite(Friend friendId, uint64 lobbyId) frd.window_state |= window_state_lobby_invite; // Make sure don't have rich presence invite and a lobby invite (it should not happen but who knows) frd.window_state &= ~window_state_rich_invite; - NotifyUser(i->second, i->first.name() + " invited you to join a game"); + AddInviteNotification(*i); + NotifyUser(i->second); } } @@ -257,7 +257,8 @@ void Steam_Overlay::SetRichInvite(Friend friendId, const char* connect_str) frd.window_state |= window_state_rich_invite; // Make sure don't have rich presence invite and a lobby invite (it should not happen but who knows) frd.window_state &= ~window_state_lobby_invite; - NotifyUser(i->second, i->first.name() + " invited you to join a game"); + AddInviteNotification(*i); + NotifyUser(i->second); } } @@ -285,13 +286,14 @@ void Steam_Overlay::FriendDisconnect(Friend _friend) friends.erase(it); } -void Steam_Overlay::AddNotification(std::string const& message) +void Steam_Overlay::AddMessageNotification(std::string const& message) { int id = find_free_notification_id(notifications); if (id != 0) { Notification notif; notif.id = id; + notif.type = notification_type_message; notif.message = message; notif.start_time = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()); notifications.emplace_back(notif); @@ -300,6 +302,40 @@ void Steam_Overlay::AddNotification(std::string const& message) PRINT_DEBUG("No more free id to create a notification window\n"); } +void Steam_Overlay::AddAchievementNotification(nlohmann::json const& ach) +{ + int id = find_free_notification_id(notifications); + if (id != 0) + { + Notification notif; + notif.id = id; + notif.type = notification_type_achievement; + // Load achievement image + notif.message = ach["displayName"].get() + "\n" + ach["description"].get(); + notif.start_time = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()); + notifications.emplace_back(notif); + } + else + PRINT_DEBUG("No more free id to create a notification window\n"); +} + +void Steam_Overlay::AddInviteNotification(std::pair& wnd_state) +{ + int id = find_free_notification_id(notifications); + if (id != 0) + { + Notification notif; + notif.id = id; + notif.type = notification_type_invite; + notif.frd = &wnd_state; + notif.message = wnd_state.first.name() + " invited you to join a game"; + notif.start_time = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()); + notifications.emplace_back(notif); + } + else + PRINT_DEBUG("No more free id to create a notification window\n"); +} + bool Steam_Overlay::FriendHasLobby(uint64 friend_id) { Steam_Friends* steamFriends = get_steam_client()->steam_friends; @@ -341,13 +377,13 @@ void Steam_Overlay::BuildContextMenu(Friend const& frd, friend_window_state& sta // If we have the same appid, activate the invite/join buttons if (settings->get_local_game_id().AppID() == frd.appid()) { - if (IHaveLobby() && ImGui::Button("Invite")) + if (IHaveLobby() && ImGui::Button("Invite###PopupInvite")) { state.window_state |= window_state_invite; has_friend_action.push(frd); close_popup = true; } - if (FriendHasLobby(frd.id()) && ImGui::Button("Join")) + if (FriendHasLobby(frd.id()) && ImGui::Button("Join###PopupJoin")) { state.window_state |= window_state_join; has_friend_action.push(frd); @@ -499,9 +535,26 @@ void Steam_Overlay::BuildNotifications(int width, int height) ImGui::SetNextWindowPos(ImVec2((float)width - width * Notification::width, Notification::height * font_size * i )); ImGui::SetNextWindowSize(ImVec2( width * Notification::width, Notification::height * font_size )); ImGui::Begin(std::to_string(it->id).c_str(), nullptr, ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoBringToFrontOnFocus | - ImGuiWindowFlags_NoFocusOnAppearing | ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoMouseInputs); + ImGuiWindowFlags_NoFocusOnAppearing | ImGuiWindowFlags_NoDecoration); - ImGui::TextWrapped("%s", it->message.c_str()); + switch (it->type) + { + case notification_type_achievement: + ImGui::TextWrapped("%s", it->message.c_str()); + break; + case notification_type_invite: + { + ImGui::TextWrapped("%s", it->message.c_str()); + if (ImGui::Button("Join")) + { + has_friend_action.push(it->frd->first); + it->start_time = std::chrono::seconds(0); + } + } + break; + case notification_type_message: + ImGui::TextWrapped("%s", it->message.c_str()); break; + } ImGui::End(); @@ -619,7 +672,8 @@ void Steam_Overlay::Callback(Common_Message *msg) friend_info->second.window_state |= window_state_need_attention; } - NotifyUser(friend_info->second, friend_info->first.name() + " says: " + steam_message.message()); + AddMessageNotification(friend_info->first.name() + " says: " + steam_message.message()); + NotifyUser(friend_info->second); } } } diff --git a/overlay_experimental/steam_overlay.h b/overlay_experimental/steam_overlay.h index d73b120..95f76c7 100644 --- a/overlay_experimental/steam_overlay.h +++ b/overlay_experimental/steam_overlay.h @@ -41,10 +41,17 @@ struct Friend_Less } }; +enum notification_type +{ + notification_type_message = 0, + notification_type_invite, + notification_type_achievement, +}; + struct Notification { static constexpr float width = 0.25; - static constexpr float height = 4.0; + static constexpr float height = 5.0; static constexpr std::chrono::milliseconds fade_in = std::chrono::milliseconds(2000); static constexpr std::chrono::milliseconds fade_out = std::chrono::milliseconds(2000); static constexpr std::chrono::milliseconds show_time = std::chrono::milliseconds(6000) + fade_in + fade_out; @@ -55,8 +62,10 @@ struct Notification static constexpr float max_alpha = 0.5f; int id; + uint8 type; std::chrono::seconds start_time; std::string message; + std::pair* frd; }; #ifndef NO_OVERLAY @@ -97,7 +106,7 @@ class Steam_Overlay bool FriendHasLobby(uint64 friend_id); bool IHaveLobby(); - void NotifyUser(friend_window_state& friend_state, std::string const& message); + void NotifyUser(friend_window_state& friend_state); // Right click on friend void BuildContextMenu(Friend const& frd, friend_window_state &state); @@ -136,7 +145,9 @@ public: void FriendConnect(Friend _friend); void FriendDisconnect(Friend _friend); - void AddNotification(std::string const& message); + void AddMessageNotification(std::string const& message); + void AddAchievementNotification(nlohmann::json const& ach); + void AddInviteNotification(std::pair &wnd_state); }; #else