arcdps\extras\message/
squad.rs

1use crate::{strip_account_prefix, util::str_from_cstr_len};
2use bitflags::bitflags;
3use chrono::{DateTime, FixedOffset};
4use std::ffi::c_char;
5
6#[cfg(feature = "serde")]
7use serde::{Deserialize, Serialize};
8
9#[cfg(feature = "strum")]
10use strum::{Display, EnumCount, EnumIter, IntoStaticStr, VariantNames};
11
12/// A squad/party chat message.
13///
14/// Strings are available for the duration of the call.
15/// If you need it for longer than that, consider converting it to [`SquadMessageOwned`].
16///
17/// ```no_run
18/// # use arcdps::extras::{SquadMessage, SquadMessageOwned};
19/// # let message: &SquadMessage = todo!();
20/// let owned = message.to_owned();
21/// let owned: SquadMessageOwned = message.into();
22/// ```
23#[derive(Debug, Clone)]
24#[repr(C)]
25pub struct SquadMessage {
26    /// A unique identifier for the channel this chat message was sent over.
27    ///
28    /// Can be used to, for example, differentiate between squad messages sent to different squads.
29    pub channel_id: u32,
30
31    /// Whether the message is sent in a party or a squad.
32    ///
33    /// Note that messages sent to the party chat while in a squad will have the type [`ChannelType::Squad`].
34    pub channel_type: ChannelType,
35
36    /// The subgroup the message was sent to, or `0` if it was sent to the entire squad.
37    pub subgroup: u8,
38
39    /// The lowest bit of this field will be set to `1` if the message is a broadcast, and `0` if it is not a broadcast.
40    /// The upper bits of this field may be used in a later version and **must not** be interpreted.
41    flags: u8,
42
43    /// Unused padding.
44    _unused1: u8,
45
46    /// Null terminated iso8601 formatted string denoting when this message was
47    /// received by the server, e.g. `"2022-07-09T11:45:24.888Z"`.
48    /// This is the "absolute ordering" for chat messages,
49    /// however the time can potentially differ several seconds between the client and server because of latency and clock skew.
50    ///
51    /// The string is only valid for the duration of the call.
52    timestamp: *const c_char,
53    timestamp_length: u64,
54
55    /// Null terminated account name of the player that sent the message, including leading ':'.
56    ///
57    /// The string is only valid for the duration of the call.
58    account_name: *const c_char,
59    account_name_length: u64,
60
61    /// Null terminated character name of the player that sent the message.
62    ///
63    /// The string is only valid for the duration of the call.
64    character_name: *const c_char,
65    character_name_length: u64,
66
67    /// Null terminated string containing the content of the message that was sent.
68    ///
69    /// The string is only valid for the duration of the call.
70    text: *const c_char,
71    text_length: u64,
72}
73
74impl SquadMessage {
75    /// Converts the squad message to its owned counterpart.
76    #[inline]
77    pub fn to_owned(&self) -> SquadMessageOwned {
78        self.into()
79    }
80
81    /// Returns the raw message flags.
82    #[inline]
83    pub fn flags_raw(&self) -> u8 {
84        self.flags
85    }
86
87    /// Returns the message flags.
88    #[inline]
89    pub fn flags(&self) -> SquadMessageFlags {
90        SquadMessageFlags::from_bits_truncate(self.flags)
91    }
92
93    /// Returns the message flags.
94    #[inline]
95    pub fn is_broadcast(&self) -> bool {
96        self.flags().contains(SquadMessageFlags::IS_BROADCAST)
97    }
98
99    /// Returns the timestamp as string.
100    #[inline]
101    pub fn timestamp_str(&self) -> &str {
102        unsafe { str_from_cstr_len(self.timestamp, self.timestamp_length) }
103    }
104
105    /// Returns the timestamp string as raw pointer.
106    #[inline]
107    pub fn timestamp_ptr(&self) -> *const c_char {
108        self.timestamp
109    }
110
111    /// Returns the timestamp string length.
112    #[inline]
113    pub fn timestamp_len(&self) -> usize {
114        self.timestamp_length as _
115    }
116
117    /// Returns the timestamp when the message was received.
118    ///
119    /// This is the "absolute ordering" for chat messages,
120    /// however the time can potentially differ several seconds between the client and server because of latency and clock skew.
121    #[inline]
122    pub fn timestamp(&self) -> Option<DateTime<FixedOffset>> {
123        DateTime::parse_from_rfc3339(self.timestamp_str()).ok()
124    }
125
126    /// Returns the account name of the player that sent the message.
127    #[inline]
128    pub fn account_name(&self) -> &str {
129        let account_name =
130            unsafe { str_from_cstr_len(self.account_name, self.account_name_length) };
131        strip_account_prefix(account_name)
132    }
133
134    /// Returns the account name as raw pointer.
135    #[inline]
136    pub fn account_name_ptr(&self) -> *const c_char {
137        self.account_name
138    }
139
140    /// Returns the account name length.
141    #[inline]
142    pub fn account_name_len(&self) -> usize {
143        self.account_name_length as _
144    }
145
146    /// Returns the character name of the player that sent the message.
147    #[inline]
148    pub fn character_name(&self) -> &str {
149        unsafe { str_from_cstr_len(self.character_name, self.character_name_length) }
150    }
151
152    /// Returns the character name as raw pointer.
153    #[inline]
154    pub fn character_name_ptr(&self) -> *const c_char {
155        self.character_name
156    }
157
158    /// Returns the account name length.
159    #[inline]
160    pub fn character_name_len(&self) -> usize {
161        self.character_name_length as _
162    }
163
164    /// Returns the text content of the message.
165    #[inline]
166    pub fn text(&self) -> &str {
167        unsafe { str_from_cstr_len(self.text, self.text_length) }
168    }
169
170    /// Returns the text as raw pointer.
171    #[inline]
172    pub fn text_ptr(&self) -> *const c_char {
173        self.text
174    }
175
176    /// Returns the account name length.
177    #[inline]
178    pub fn text_len(&self) -> usize {
179        self.text_length as _
180    }
181}
182
183/// [`SquadMessage`] with owned [`String`] fields.
184#[derive(Debug, Clone)]
185#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
186pub struct SquadMessageOwned {
187    /// A unique identifier for the channel this chat message was sent over.
188    ///
189    /// Can be used to, for example, differentiate between squad messages sent to different squads.
190    pub channel_id: u32,
191
192    /// Whether the message is sent in a party or a squad.
193    ///
194    /// Note that messages sent to the party chat while in a squad will have the type [`ChannelType::Squad`].
195    pub channel_type: ChannelType,
196
197    /// The subgroup the message was sent to, or `0` if it was sent to the entire squad.
198    pub subgroup: u8,
199
200    /// Whether the message is a broadcast.
201    pub flags: SquadMessageFlags,
202
203    /// Timestamp when the message was received.
204    ///
205    /// This is the "absolute ordering" for chat messages,
206    /// however the time can potentially differ several seconds between the client and server because of latency and clock skew.
207    pub timestamp: Option<DateTime<FixedOffset>>,
208
209    /// Account name of the player that sent the message.
210    pub account_name: String,
211
212    /// Character name of the player that sent the message.
213    pub character_name: String,
214
215    /// Content of the message.
216    pub text: String,
217}
218
219impl From<SquadMessage> for SquadMessageOwned {
220    #[inline]
221    fn from(msg: SquadMessage) -> Self {
222        (&msg).into()
223    }
224}
225
226impl From<&SquadMessage> for SquadMessageOwned {
227    #[inline]
228    fn from(msg: &SquadMessage) -> Self {
229        Self {
230            channel_id: msg.channel_id,
231            channel_type: msg.channel_type,
232            subgroup: msg.subgroup,
233            flags: SquadMessageFlags::from_bits_truncate(msg.flags),
234            timestamp: msg.timestamp(),
235            account_name: msg.account_name().to_owned(),
236            character_name: msg.character_name().to_owned(),
237            text: msg.text().to_owned(),
238        }
239    }
240}
241
242bitflags! {
243    /// Squad message flags.
244    #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
245    #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
246    pub struct SquadMessageFlags : u8 {
247        /// Message is a broadcast.
248        const IS_BROADCAST = 1;
249    }
250}
251
252/// Type of message channel.
253#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
254#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
255#[cfg_attr(
256    feature = "strum",
257    derive(Display, EnumCount, EnumIter, IntoStaticStr, VariantNames)
258)]
259#[repr(u8)]
260pub enum ChannelType {
261    Party = 0,
262    Squad = 1,
263    Reserved = 2,
264    Invalid = 3,
265}