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}