arcdps\extras/
user.rs

1//! User information provided by Unofficial Extras.
2
3use crate::util::{str_from_cstr, strip_account_prefix};
4use num_enum::{IntoPrimitive, TryFromPrimitive};
5use std::{os::raw::c_char, slice};
6
7#[cfg(feature = "serde")]
8use serde::{Deserialize, Serialize};
9
10#[cfg(feature = "strum")]
11use strum::{Display, EnumCount, EnumIter, IntoStaticStr, VariantNames};
12
13/// Role of a user in the squad.
14#[derive(
15    Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, IntoPrimitive, TryFromPrimitive,
16)]
17#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
18#[cfg_attr(
19    feature = "strum",
20    derive(Display, EnumCount, EnumIter, IntoStaticStr, VariantNames)
21)]
22#[repr(u8)]
23pub enum UserRole {
24    /// User is leader (commander tag).
25    SquadLeader = 0,
26
27    /// User is lieutenant.
28    Lieutenant = 1,
29
30    /// User is regular member.
31    Member = 2,
32
33    /// User is invited.
34    Invited = 3,
35
36    /// User has requested to join.
37    Applied = 4,
38
39    /// User has left.
40    None = 5,
41
42    /// Internal only.
43    Invalid = 6,
44}
45
46/// Information about a player related to the squad.
47///
48/// Strings are available for the duration of the call.
49/// If you need it for longer than that, consider converting it to [`UserInfoOwned`].
50///
51/// ```no_run
52/// # use arcdps::extras::{UserInfo, UserInfoOwned};
53/// # let user: UserInfo = todo!();
54/// let owned = user.to_owned();
55/// let owned: UserInfoOwned = user.into();
56/// ```
57#[derive(Debug)]
58#[repr(C)]
59pub struct UserInfo {
60    /// Account name with leading `':'`.
61    account_name: *const c_char,
62
63    /// Unix timestamp when the user joined the squad.
64    ///
65    /// `0` if time could not be determined.
66    pub join_time: u64,
67
68    /// Role in squad, or [`UserRole::None`] if the user was removed from the squad.
69    pub role: UserRole,
70
71    /// Subgroup the user is in.
72    ///
73    /// `0` when no subgroup could be found, which is either the first subgroup or no subgroup.
74    pub subgroup: u8,
75
76    /// Whether this player is ready or not (in a squad ready check).
77    ///
78    /// ### Remarks
79    /// `role` set to [`UserRole::SquadLeader`] and `ready_status == true` implies that a ready check was just started.
80    /// Similarly, `role` set to [`UserRole::SquadLeader`] and `ready_status == false` implies that a ready check either finished or was cancelled.
81    /// If everyone in the squad had an event sent with `ready_status == true` then that means that the ready check finished successfully.
82    /// After which there will be events sent for each user where their `ready_status == false`.
83    pub ready_status: bool,
84
85    /// Unused space.
86    pub _unused1: u8,
87
88    /// Unused space.
89    pub _unused2: u32,
90}
91
92impl UserInfo {
93    /// Returns the user account name without leading `':'`.
94    #[inline]
95    pub fn account_name(&self) -> Option<&str> {
96        unsafe { str_from_cstr(self.account_name).map(strip_account_prefix) }
97    }
98
99    /// Returns the raw pointer to the user account name.
100    #[inline]
101    pub fn account_name_ptr(&self) -> *const c_char {
102        self.account_name
103    }
104
105    /// Converts the [`UserInfo`] to the owned version [`UserInfoOwned`].
106    #[inline]
107    pub fn to_owned(self) -> UserInfoOwned {
108        self.into()
109    }
110}
111
112/// [`UserInfo`] with an owned [`String`] name.
113#[derive(Debug, Clone)]
114#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
115pub struct UserInfoOwned {
116    /// Account name, without leading ':'.
117    pub account_name: Option<String>,
118
119    /// Unix timestamp when the user joined the squad.
120    ///
121    /// `0` if time could not be determined.
122    pub join_time: u64,
123
124    /// Role in squad, or [`UserRole::None`] if the user was removed from the squad.
125    pub role: UserRole,
126
127    /// Subgroup the user is in.
128    ///
129    /// `0` when no subgroup could be found, which is either the first subgroup or no subgroup.
130    pub subgroup: u8,
131
132    /// Whether this player is ready or not (in a squad ready check).
133    ///
134    /// ### Remarks
135    /// `role` set to [`UserRole::SquadLeader`] and `ready_status == true` implies that a ready check was just started.
136    /// Similarly, `role` set to [`UserRole::SquadLeader`] and `ready_status == false` implies that a ready check either finished or was cancelled.
137    /// If everyone in the squad had an event sent with `ready_status == true` then that means that the ready check finished successfully.
138    /// After which there will be events sent for each user where their `ready_status == false`.
139    pub ready_status: bool,
140}
141
142impl From<UserInfo> for UserInfoOwned {
143    #[inline]
144    fn from(user: UserInfo) -> Self {
145        Self {
146            account_name: user.account_name().map(|x| x.to_string()),
147            join_time: user.join_time,
148            role: user.role,
149            subgroup: user.subgroup,
150            ready_status: user.ready_status,
151        }
152    }
153}
154
155/// Iterator over changed users.
156pub type UserInfoIter<'a> = slice::Iter<'a, UserInfo>;
157
158/// Helper to generate an iterator over [`UserInfo`] structs.
159#[inline]
160pub unsafe fn to_user_info_iter<'a>(ptr: *const UserInfo, len: u64) -> UserInfoIter<'a> {
161    slice::from_raw_parts(ptr, len as usize).iter()
162}