diff options
| author | Alex Butler <[email protected]> | 2019-05-16 11:59:20 +0100 |
|---|---|---|
| committer | Matthew Collins <[email protected]> | 2019-05-16 17:35:04 +0100 |
| commit | 7d2ed5ea57f3ea769e659d8c80fd52d60624553e (patch) | |
| tree | 3156afb74c6f2249bd1beb37cb9761b1c40b723e /src | |
| parent | Skip fields with missing enum types (diff) | |
| download | steamworks-rs-7d2ed5ea57f3ea769e659d8c80fd52d60624553e.tar.xz steamworks-rs-7d2ed5ea57f3ea769e659d8c80fd52d60624553e.zip | |
Add stats & achievements API
Diffstat (limited to 'src')
| -rw-r--r-- | src/user_stats.rs | 33 | ||||
| -rw-r--r-- | src/user_stats/stat_callback.rs | 113 | ||||
| -rw-r--r-- | src/user_stats/stats.rs | 80 |
3 files changed, 225 insertions, 1 deletions
diff --git a/src/user_stats.rs b/src/user_stats.rs index 745b76a..2ab976b 100644 --- a/src/user_stats.rs +++ b/src/user_stats.rs @@ -1,4 +1,7 @@ +pub mod stats; +mod stat_callback; +pub use self::stat_callback::*; use super::*; /// Access to the steam user interface @@ -141,6 +144,34 @@ impl <Manager> UserStats<Manager> { }); } } + + /// 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(Debug)] @@ -211,4 +242,4 @@ fn test() { single.run_callbacks(); ::std::thread::sleep(::std::time::Duration::from_millis(100)); } -}
\ No newline at end of file +} diff --git a/src/user_stats/stat_callback.rs b/src/user_stats/stat_callback.rs new file mode 100644 index 0000000..da62e6a --- /dev/null +++ b/src/user_stats/stat_callback.rs @@ -0,0 +1,113 @@ +use super::*; + +/// Callback type after calling +/// [`request_current_stats()`](struct.UserStats.html#method.request_current_stats). +/// +/// # Example +/// +/// ```no_run +/// # use steamworks::*; +/// # let (client, single) = steamworks::Client::init().unwrap(); +/// let callback_handle = client.register_callback(|val: UserStatsReceived| { +/// if val.result.is_err() { +/// // ... +/// } +/// }); +/// ``` +#[derive(Debug)] +pub struct UserStatsReceived { + pub steam_id: SteamId, + pub game_id: GameId, + pub result: Result<(), SteamError>, +} + +unsafe impl Callback for UserStatsReceived { + const ID: i32 = CALLBACK_BASE_ID + 1; + const SIZE: i32 = std::mem::size_of::<sys::UserStatsReceived_t>() as i32; + + unsafe fn from_raw(raw: *mut libc::c_void) -> Self { + let val = &mut *(raw as *mut sys::UserStatsReceived_t); + Self { + steam_id: SteamId(val.m_steamIDUser.0), + game_id: GameId(val.m_nGameID), + result: match val.m_eResult { + sys::EResult::EResultOK => Ok(()), + err => Err(err.into()), + }, + } + } +} + +/// Callback triggered by [`store()`](stats/struct.StatsHelper.html#method.store). +/// +/// # Example +/// +/// ```no_run +/// # use steamworks::*; +/// # let (client, single) = steamworks::Client::init().unwrap(); +/// let callback_handle = client.register_callback(|val: UserStatsStored| { +/// if val.result.is_err() { +/// // ... +/// } +/// }); +/// ``` +#[derive(Debug)] +pub struct UserStatsStored { + pub game_id: GameId, + pub result: Result<(), SteamError>, +} + +unsafe impl Callback for UserStatsStored { + const ID: i32 = CALLBACK_BASE_ID + 2; + const SIZE: i32 = std::mem::size_of::<sys::UserStatsStored_t>() as i32; + + unsafe fn from_raw(raw: *mut libc::c_void) -> Self { + let val = &mut *(raw as *mut sys::UserStatsStored_t); + Self { + game_id: GameId(val.m_nGameID), + result: match val.m_eResult { + sys::EResult::EResultOK => Ok(()), + err => Err(err.into()), + }, + } + } +} + +/// Result of a request to store the achievements on the server, or an "indicate progress" call. +/// If both `current_progress` and `max_progress` are zero, that means the achievement has been +/// fully unlocked. +/// +/// # Example +/// +/// ```no_run +/// # use steamworks::*; +/// # let (client, single) = steamworks::Client::init().unwrap(); +/// let callback_handle = client.register_callback(|val: UserAchievementStored| { +/// // ... +/// }); +/// ``` +#[derive(Debug)] +pub struct UserAchievementStored { + pub game_id: GameId, + pub achievement_name: String, + /// Current progress towards the achievement. + pub current_progress: u32, + /// The total amount of progress required to unlock. + pub max_progress: u32, +} + +unsafe impl Callback for UserAchievementStored { + const ID: i32 = CALLBACK_BASE_ID + 3; + const SIZE: i32 = std::mem::size_of::<sys::UserAchievementStored_t>() as i32; + + unsafe fn from_raw(raw: *mut libc::c_void) -> Self { + let val = &mut *(raw as *mut sys::UserAchievementStored_t); + let name = CStr::from_ptr(val.m_rgchAchievementName.as_ptr()).to_owned(); + Self { + game_id: GameId(val.m_nGameID), + achievement_name: name.into_string().unwrap(), + current_progress: val.m_nCurProgress, + max_progress: val.m_nMaxProgress, + } + } +} diff --git a/src/user_stats/stats.rs b/src/user_stats/stats.rs new file mode 100644 index 0000000..56d650f --- /dev/null +++ b/src/user_stats/stats.rs @@ -0,0 +1,80 @@ +use super::*; + +/// Achievement API. +/// +/// Methods require +/// [`request_current_stats()`](../struct.UserStats.html#method.request_current_stats) +/// to have been called and a successful [`UserStatsReceived`](../struct.UserStatsReceived.html) +/// callback processed. +/// +/// # Example +/// +/// ```no_run +/// # use steamworks::*; +/// # let (client, single) = steamworks::Client::init().unwrap(); +/// // Unlock the 'WIN_THE_GAME' achievement +/// client.user_stats().achievement("WIN_THE_GAME").set()?; +/// # Err(()) +/// ``` +pub struct AchievementHelper<'parent, M> { + pub(crate) name: CString, + pub(crate) parent: &'parent UserStats<M>, +} + +impl<M> AchievementHelper<'_, M> { + /// Gets the unlock status of the Achievement. + /// + /// This call only modifies Steam's in-memory state so it is quite cheap. To send the unlock + /// status to the server and to trigger the Steam overlay notification you must call + /// [`store_stats()`](../struct.UserStats.html#method.store_stats). + /// + /// Fails if this achievement's 'API Name' is unknown, or unsuccessful + /// [`UserStatsReceived`](../struct.UserStatsReceived.html). + pub fn get(&self) -> Result<bool, ()> { + unsafe { + let mut achieved = false; + let success = sys::SteamAPI_ISteamUserStats_GetAchievement( + self.parent.user_stats, + self.name.as_ptr() as *const _, + &mut achieved as *mut _, + ); + if success { Ok(achieved) } else { Err(()) } + } + } + + /// Unlocks an achievement. + /// + /// This call only modifies Steam's in-memory state so it is quite cheap. To send the unlock + /// status to the server and to trigger the Steam overlay notification you must call + /// [`store_stats()`](../struct.UserStats.html#method.store_stats). + /// + /// Fails if this achievement's 'API Name' is unknown, or unsuccessful + /// [`UserStatsReceived`](../struct.UserStatsReceived.html). + pub fn set(&self) -> Result<(), ()> { + let success = unsafe { + sys::SteamAPI_ISteamUserStats_SetAchievement( + self.parent.user_stats, + self.name.as_ptr() as *const _, + ) + }; + if success { Ok(()) } else { Err(()) } + } + + /// Resets the unlock status of an achievement. + /// + /// This call only modifies Steam's in-memory state so it is quite cheap. To send the unlock + /// status to the server and to trigger the Steam overlay notification you must call + /// [`store_stats()`](../struct.UserStats.html#method.store_stats). + /// + /// Fails if this achievement's 'API Name' is unknown, or unsuccessful + /// [`UserStatsReceived`](../struct.UserStatsReceived.html). + pub fn clear(&self) -> Result<(), ()> { + let success = unsafe { + sys::SteamAPI_ISteamUserStats_ClearAchievement( + self.parent.user_stats, + self.name.as_ptr() as *const _, + ) + }; + if success { Ok(()) } else { Err(()) } + } +} |