pub mod stats; mod stat_callback; pub use self::stat_callback::*; use super::*; #[cfg(test)] use serial_test_derive::serial; /// Access to the steam user interface pub struct UserStats { pub(crate) user_stats: *mut sys::ISteamUserStats, pub(crate) inner: Arc>, } const CALLBACK_BASE_ID: i32 = 1100; impl UserStats { pub fn find_leaderboard(&self, name: &str, cb: F) where F: FnOnce(Result, SteamError>) + 'static + Send { unsafe { let name = CString::new(name).unwrap(); let api_call = sys::SteamAPI_ISteamUserStats_FindLeaderboard(self.user_stats, name.as_ptr() as *const _); register_call_result::( &self.inner, api_call, CALLBACK_BASE_ID + 4, move |v, io_error| { cb(if io_error { Err(SteamError::IOFailure) } else { Ok(if v.m_bLeaderboardFound != 0 { Some(Leaderboard(v.m_hSteamLeaderboard)) } else { None }) }) }); } } pub fn find_or_create_leaderboard(&self, name: &str, sort_method: LeaderboardSortMethod, display_type: LeaderboardDisplayType, cb: F) where F: FnOnce(Result, SteamError>) + 'static + Send { unsafe { let name = CString::new(name).unwrap(); let sort_method = match sort_method { LeaderboardSortMethod::Ascending => sys::ELeaderboardSortMethod::k_ELeaderboardSortMethodAscending, LeaderboardSortMethod::Descending => sys::ELeaderboardSortMethod::k_ELeaderboardSortMethodDescending, }; let display_type = match display_type { LeaderboardDisplayType::Numeric => sys::ELeaderboardDisplayType::k_ELeaderboardDisplayTypeNumeric, LeaderboardDisplayType::TimeSeconds => sys::ELeaderboardDisplayType::k_ELeaderboardDisplayTypeTimeSeconds, LeaderboardDisplayType::TimeMilliSeconds => sys::ELeaderboardDisplayType::k_ELeaderboardDisplayTypeTimeMilliSeconds, }; let api_call = sys::SteamAPI_ISteamUserStats_FindOrCreateLeaderboard(self.user_stats, name.as_ptr() as *const _, sort_method, display_type); register_call_result::( &self.inner, api_call, CALLBACK_BASE_ID + 4, move |v, io_error| { cb(if io_error { Err(SteamError::IOFailure) } else { Ok(if v.m_bLeaderboardFound != 0 { Some(Leaderboard(v.m_hSteamLeaderboard)) } else { None }) }) } ); } } pub fn upload_leaderboard_score(&self, leaderboard: &Leaderboard, method: UploadScoreMethod, score: i32, details: &[i32], cb: F) where F: FnOnce(Result, SteamError>) + 'static + Send { unsafe { let method = match method { UploadScoreMethod::KeepBest => sys::ELeaderboardUploadScoreMethod::k_ELeaderboardUploadScoreMethodKeepBest, UploadScoreMethod::ForceUpdate => sys::ELeaderboardUploadScoreMethod::k_ELeaderboardUploadScoreMethodForceUpdate, }; let api_call = sys::SteamAPI_ISteamUserStats_UploadLeaderboardScore(self.user_stats, leaderboard.0, method, score, details.as_ptr(), details.len() as _); register_call_result::( &self.inner, api_call, CALLBACK_BASE_ID + 6, move |v, io_error| { cb(if io_error { Err(SteamError::IOFailure) } else { Ok(if v.m_bSuccess != 0 { Some(LeaderboardScoreUploaded { score: v.m_nScore, was_changed: v.m_bScoreChanged != 0, global_rank_new: v.m_nGlobalRankNew as _, global_rank_previous: v.m_nGlobalRankPrevious as _, }) } else { None }) }) }); } } pub fn download_leaderboard_entries( &self, leaderboard: &Leaderboard, request: LeaderboardDataRequest, start: usize, end: usize, max_details_len: usize, cb: F ) where F: FnOnce(Result, SteamError>) + 'static + Send { unsafe { let request = match request { LeaderboardDataRequest::Global => sys::ELeaderboardDataRequest::k_ELeaderboardDataRequestGlobal, LeaderboardDataRequest::GlobalAroundUser => sys::ELeaderboardDataRequest::k_ELeaderboardDataRequestGlobalAroundUser, LeaderboardDataRequest::Friends => sys::ELeaderboardDataRequest::k_ELeaderboardDataRequestFriends, }; let api_call = sys::SteamAPI_ISteamUserStats_DownloadLeaderboardEntries(self.user_stats, leaderboard.0, request, start as _, end as _); let user_stats = self.user_stats as isize; register_call_result::( &self.inner, api_call, CALLBACK_BASE_ID + 5, move |v, io_error| { cb(if io_error { Err(SteamError::IOFailure) } else { let len = v.m_cEntryCount; let mut entries = Vec::with_capacity(len as usize); for idx in 0 .. len { let mut entry: sys::LeaderboardEntry_t = std::mem::zeroed(); let mut details = Vec::with_capacity(max_details_len); sys::SteamAPI_ISteamUserStats_GetDownloadedLeaderboardEntry(user_stats as *mut _, v.m_hSteamLeaderboardEntries, idx, &mut entry, details.as_mut_ptr(), max_details_len as _); details.set_len(entry.m_cDetails as usize); entries.push(LeaderboardEntry { user: SteamId(entry.m_steamIDUser.m_steamid.m_unAll64Bits), global_rank: entry.m_nGlobalRank, score: entry.m_nScore, details, }) } Ok(entries) }) }); } } /// Triggers a [`UserStatsReceived`](./struct.UserStatsReceived.html) callback. pub fn request_current_stats(&self) { unsafe { sys::SteamAPI_ISteamUserStats_RequestCurrentStats(self.user_stats); } } /// Send the changed stats and achievements data to the server for permanent storage. /// /// * Triggers a [`UserStatsStored`](../struct.UserStatsStored.html) callback if successful. /// * Triggers a [`UserAchievementStored`](../struct.UserAchievementStored.html) callback /// if achievements have been unlocked. /// /// Requires [`request_current_stats()`](#method.request_current_stats) to have been called /// and a successful [`UserStatsReceived`](./struct.UserStatsReceived.html) callback processed. pub fn store_stats(&self) -> Result<(), ()> { let success = unsafe { sys::SteamAPI_ISteamUserStats_StoreStats(self.user_stats) }; if success { Ok(()) } else { Err(()) } } /// Access achievement API for a given achievement 'API Name'. /// /// Requires [`request_current_stats()`](#method.request_current_stats) to have been called /// and a successful [`UserStatsReceived`](./struct.UserStatsReceived.html) callback processed. #[inline] #[must_use] pub fn achievement(&self, name: &str) -> stats::AchievementHelper<'_, Manager> { stats::AchievementHelper { name: CString::new(name).unwrap(), parent: self } } } #[derive(Clone, Debug)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub struct LeaderboardEntry { pub user: SteamId, pub global_rank: i32, pub score: i32, pub details: Vec, } pub enum LeaderboardDataRequest { Global, GlobalAroundUser, Friends, } #[derive(Clone, Debug)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub struct LeaderboardScoreUploaded { pub score: i32, pub was_changed: bool, pub global_rank_new: i32, pub global_rank_previous: i32, } #[derive(Clone, Debug)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub enum UploadScoreMethod { KeepBest, ForceUpdate, } #[derive(Clone, Debug)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub enum LeaderboardSortMethod { Ascending, Descending, } #[derive(Clone, Debug)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub enum LeaderboardDisplayType { Numeric, TimeSeconds, TimeMilliSeconds, } #[derive(Clone, Debug)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub struct Leaderboard(u64); #[test] #[serial] fn test() { let (client, single) = Client::init().unwrap(); let stats = client.user_stats(); stats.find_leaderboard("steamworks_test", |lb| { println!("Got: {:?}", lb); }); let c2 = client.clone(); stats.find_or_create_leaderboard("steamworks_test_created", LeaderboardSortMethod::Descending, LeaderboardDisplayType::TimeMilliSeconds, move |lb| { println!("Got: {:?}", lb); if let Some(lb) = lb.ok().and_then(|v| v) { c2.user_stats().upload_leaderboard_score(&lb, UploadScoreMethod::ForceUpdate, 1337, &[1, 2, 3, 4], |v| { println!("Upload: {:?}", v); }); c2.user_stats().download_leaderboard_entries(&lb, LeaderboardDataRequest::Global, 0, 200, 10, |v| { println!("Download: {:?}", v); }); } }); for _ in 0 .. 50 { single.run_callbacks(); ::std::thread::sleep(::std::time::Duration::from_millis(100)); } }