Skip to main content

sl_types/
chat.rs

1//! Types related to SL chat
2
3#[cfg(feature = "chumsky")]
4use chumsky::{IterParser as _, Parser, text::digits};
5
6/// represents a Second Life chat channel
7#[derive(
8    Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, serde::Serialize, serde::Deserialize,
9)]
10#[expect(
11    clippy::module_name_repetitions,
12    reason = "this type is used outside of this module"
13)]
14pub struct ChatChannel(pub i32);
15
16/// parse a chat channel
17///
18/// # Errors
19///
20/// returns an error if the string could not be parsed
21#[cfg(feature = "chumsky")]
22#[must_use]
23#[expect(
24    clippy::module_name_repetitions,
25    reason = "this parser is used outside of this module"
26)]
27pub fn chat_channel_parser<'src>()
28-> impl Parser<'src, &'src str, ChatChannel, chumsky::extra::Err<chumsky::error::Rich<'src, char>>>
29{
30    digits(10).collect::<String>().try_map(|d, span| {
31        let d: i32 = d
32            .parse()
33            .map_err(|e| chumsky::error::Rich::custom(span, format!("{e:?}")))?;
34        Ok(ChatChannel(d))
35    })
36}
37
38impl std::fmt::Display for ChatChannel {
39    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
40        write!(f, "{}", self.0)
41    }
42}
43
44impl std::str::FromStr for ChatChannel {
45    type Err = <i32 as std::str::FromStr>::Err;
46
47    fn from_str(s: &str) -> Result<Self, Self::Err> {
48        <i32 as std::str::FromStr>::from_str(s).map(ChatChannel)
49    }
50}
51
52/// the public chat channel on Second Life
53pub const PUBLIC_CHANNEL: ChatChannel = ChatChannel(0);
54
55/// the combat log event chat channel on Second Life
56pub const COMBAT_CHANNEL: ChatChannel = ChatChannel(0x7FFF_FFFE);
57
58/// the script debug chat channel on Second Life
59pub const DEBUG_CHANNEL: ChatChannel = ChatChannel(0x7FFF_FFFF);
60
61/// represents a Second Life chat volume
62#[derive(
63    Debug,
64    Clone,
65    Copy,
66    PartialEq,
67    Eq,
68    PartialOrd,
69    Ord,
70    strum::EnumIs,
71    serde::Serialize,
72    serde::Deserialize,
73)]
74#[expect(
75    clippy::module_name_repetitions,
76    reason = "this type is used outside of this module"
77)]
78pub enum ChatVolume {
79    /// whisper (10m)
80    Whisper,
81    /// say (20m, default, a.k.a. chat range)
82    Say,
83    /// shout (100m)
84    Shout,
85    /// region say (the whole region)
86    RegionSay,
87}
88
89impl ChatVolume {
90    /// identify the chat volume of a message and strip it off the message
91    #[must_use]
92    pub fn volume_and_message(s: String) -> (Self, String) {
93        if let Some(whisper_message) = s.strip_prefix("whispers: ") {
94            (Self::Whisper, whisper_message.to_string())
95        } else if let Some(shout_message) = s.strip_prefix("shouts: ") {
96            (Self::Shout, shout_message.to_string())
97        } else {
98            (Self::Say, s)
99        }
100    }
101}