veloren_common/comp/
chat.rs

1use crate::{
2    comp::{BuffKind, group::Group},
3    uid::Uid,
4};
5use common_i18n::Content;
6use serde::{Deserialize, Serialize};
7use specs::{Component, DenseVecStorage};
8use std::time::{Duration, Instant};
9
10/// A player's current chat mode. These are chat types that can only be sent by
11/// the player.
12#[derive(Clone, Debug, Serialize, Deserialize)]
13pub enum ChatMode {
14    /// Private message to another player (by uuid)
15    Tell(Uid),
16    /// Talk to players within shouting distance
17    Say,
18    /// Talk to players in your region of the world
19    Region,
20    /// Talk to your current group of players
21    Group,
22    /// Talk to your faction
23    Faction(String),
24    /// Talk to every player on the server
25    World,
26}
27
28impl Component for ChatMode {
29    type Storage = DenseVecStorage<Self>;
30}
31
32impl ChatMode {
33    /// Create a message from your current chat mode and uuid
34    pub fn to_msg(
35        &self,
36        from: Uid,
37        content: Content,
38        group: Option<Group>,
39    ) -> Result<UnresolvedChatMsg, Content> {
40        let chat_type = match self {
41            ChatMode::Tell(to) => ChatType::Tell(from, *to),
42            ChatMode::Say => ChatType::Say(from),
43            ChatMode::Region => ChatType::Region(from),
44            ChatMode::Group => ChatType::Group(
45                from,
46                group.ok_or(Content::localized("command-message-group-missing"))?,
47            ),
48            ChatMode::Faction(faction) => ChatType::Faction(from, faction.clone()),
49            ChatMode::World => ChatType::World(from),
50        };
51
52        Ok(UnresolvedChatMsg { chat_type, content })
53    }
54}
55
56impl ChatMode {
57    pub const fn default() -> Self { Self::World }
58}
59
60/// Enum representing death types
61///
62/// All variants should be strictly typed, no string content.
63///
64/// If it's too complicated to create an enum for death type, consult i18n team
65#[derive(Debug, Clone, Serialize, Deserialize)]
66pub enum KillType {
67    Buff(BuffKind),
68    Melee,
69    Projectile,
70    Explosion,
71    Energy,
72    Other,
73    // Projectile(Type), TODO: add projectile name when available
74}
75
76/// Enum representing death reasons
77///
78/// All variants should be strictly typed, no string content.
79#[derive(Debug, Clone, Serialize, Deserialize)]
80pub enum KillSource {
81    Player(Uid, KillType),
82    NonPlayer(String, KillType),
83    NonExistent(KillType),
84    FallDamage,
85    Suicide,
86    Other,
87}
88
89/// List of chat types. Each one is colored differently and has its own icon.
90///
91/// This is a superset of `SpeechBubbleType`, which is a superset of `ChatMode`
92#[derive(Debug, Clone, Serialize, Deserialize)]
93pub enum ChatType<G> {
94    /// A player came online
95    Online(Uid),
96    /// A player went offline
97    Offline(Uid),
98    /// The result of chat commands
99    CommandInfo,
100    /// A chat command failed
101    CommandError,
102    /// Inform players that someone died (Source, Victim) Source may be None
103    /// (ex: fall damage)
104    Kill(KillSource, Uid),
105    /// Server notifications to a group, such as player join/leave
106    GroupMeta(G),
107    /// Server notifications to a faction, such as player join/leave
108    FactionMeta(String),
109    /// One-on-one chat (from, to)
110    Tell(Uid, Uid),
111    /// Chat with nearby players
112    Say(Uid),
113    /// Group chat
114    Group(Uid, G),
115    /// Factional chat
116    Faction(Uid, String),
117    /// Regional chat
118    Region(Uid),
119    /// World chat
120    World(Uid),
121    /// Messages sent from NPCs (Not shown in chat but as speech bubbles)
122    Npc(Uid),
123    /// From NPCs but in the chat for clients in the near vicinity
124    NpcSay(Uid),
125    /// From NPCs but in the chat for a specific client. Shows a chat bubble.
126    /// (from, to, localization variant)
127    NpcTell(Uid, Uid),
128    /// Anything else
129    Meta,
130}
131
132impl<G> ChatType<G> {
133    pub fn into_plain_msg(self, text: impl ToString) -> GenericChatMsg<G> {
134        GenericChatMsg {
135            chat_type: self,
136            content: Content::Plain(text.to_string()),
137        }
138    }
139
140    pub fn into_msg(self, content: Content) -> GenericChatMsg<G> {
141        GenericChatMsg {
142            chat_type: self,
143            content,
144        }
145    }
146
147    pub fn uid(&self) -> Option<Uid> {
148        match self {
149            ChatType::Online(_) => None,
150            ChatType::Offline(_) => None,
151            ChatType::CommandInfo => None,
152            ChatType::CommandError => None,
153            ChatType::FactionMeta(_) => None,
154            ChatType::GroupMeta(_) => None,
155            ChatType::Kill(_, _) => None,
156            ChatType::Tell(u, _t) => Some(*u),
157            ChatType::Say(u) => Some(*u),
158            ChatType::Group(u, _s) => Some(*u),
159            ChatType::Faction(u, _s) => Some(*u),
160            ChatType::Region(u) => Some(*u),
161            ChatType::World(u) => Some(*u),
162            ChatType::Npc(u) => Some(*u),
163            ChatType::NpcSay(u) => Some(*u),
164            ChatType::NpcTell(u, _t) => Some(*u),
165            ChatType::Meta => None,
166        }
167    }
168
169    /// `None` means that the chat type is automated.
170    pub fn is_private(&self) -> Option<bool> {
171        match self {
172            ChatType::Online(_)
173            | ChatType::Offline(_)
174            | ChatType::CommandInfo
175            | ChatType::CommandError
176            | ChatType::FactionMeta(_)
177            | ChatType::GroupMeta(_)
178            | ChatType::Npc(_)
179            | ChatType::NpcSay(_)
180            | ChatType::NpcTell(_, _)
181            | ChatType::Meta
182            | ChatType::Kill(_, _) => None,
183            ChatType::Tell(_, _) | ChatType::Group(_, _) | ChatType::Faction(_, _) => Some(true),
184            ChatType::Say(_) | ChatType::Region(_) | ChatType::World(_) => Some(false),
185        }
186    }
187}
188
189// Stores chat text, type
190#[derive(Debug, Clone, Serialize, Deserialize)]
191pub struct GenericChatMsg<G> {
192    pub chat_type: ChatType<G>,
193    content: Content,
194}
195
196pub type ChatMsg = GenericChatMsg<String>;
197pub type UnresolvedChatMsg = GenericChatMsg<Group>;
198
199impl<G> GenericChatMsg<G> {
200    pub const MAX_BYTES_PLAYER_CHAT_MSG: usize = 256;
201    pub const NPC_DISTANCE: f32 = 100.0;
202    pub const NPC_SAY_DISTANCE: f32 = 30.0;
203    pub const REGION_DISTANCE: f32 = 1000.0;
204    pub const SAY_DISTANCE: f32 = 100.0;
205
206    pub fn npc(uid: Uid, content: Content) -> Self {
207        let chat_type = ChatType::Npc(uid);
208        Self { chat_type, content }
209    }
210
211    pub fn npc_say(uid: Uid, content: Content) -> Self {
212        let chat_type = ChatType::NpcSay(uid);
213        Self { chat_type, content }
214    }
215
216    pub fn npc_tell(from: Uid, to: Uid, content: Content) -> Self {
217        let chat_type = ChatType::NpcTell(from, to);
218        Self { chat_type, content }
219    }
220
221    pub fn death(kill_source: KillSource, victim: Uid) -> Self {
222        Self {
223            chat_type: ChatType::Kill(kill_source, victim),
224            content: Content::Plain(String::new()),
225        }
226    }
227
228    pub fn map_group<T>(self, mut f: impl FnMut(G) -> T) -> GenericChatMsg<T> {
229        let chat_type = match self.chat_type {
230            ChatType::Online(a) => ChatType::Online(a),
231            ChatType::Offline(a) => ChatType::Offline(a),
232            ChatType::CommandInfo => ChatType::CommandInfo,
233            ChatType::CommandError => ChatType::CommandError,
234            ChatType::FactionMeta(a) => ChatType::FactionMeta(a),
235            ChatType::GroupMeta(g) => ChatType::GroupMeta(f(g)),
236            ChatType::Kill(a, b) => ChatType::Kill(a, b),
237            ChatType::Tell(a, b) => ChatType::Tell(a, b),
238            ChatType::Say(a) => ChatType::Say(a),
239            ChatType::Group(a, g) => ChatType::Group(a, f(g)),
240            ChatType::Faction(a, b) => ChatType::Faction(a, b),
241            ChatType::Region(a) => ChatType::Region(a),
242            ChatType::World(a) => ChatType::World(a),
243            ChatType::Npc(a) => ChatType::Npc(a),
244            ChatType::NpcSay(a) => ChatType::NpcSay(a),
245            ChatType::NpcTell(a, b) => ChatType::NpcTell(a, b),
246            ChatType::Meta => ChatType::Meta,
247        };
248
249        GenericChatMsg {
250            chat_type,
251            content: self.content,
252        }
253    }
254
255    pub fn get_group(&self) -> Option<&G> {
256        match &self.chat_type {
257            ChatType::GroupMeta(g) => Some(g),
258            ChatType::Group(_, g) => Some(g),
259            _ => None,
260        }
261    }
262
263    pub fn to_bubble(&self) -> Option<(SpeechBubble, Uid)> {
264        self.uid()
265            .map(|from| (SpeechBubble::new(self.content.clone(), self.icon()), from))
266    }
267
268    pub fn icon(&self) -> SpeechBubbleType {
269        match &self.chat_type {
270            ChatType::Online(_) => SpeechBubbleType::None,
271            ChatType::Offline(_) => SpeechBubbleType::None,
272            ChatType::CommandInfo => SpeechBubbleType::None,
273            ChatType::CommandError => SpeechBubbleType::None,
274            ChatType::FactionMeta(_) => SpeechBubbleType::None,
275            ChatType::GroupMeta(_) => SpeechBubbleType::None,
276            ChatType::Kill(_, _) => SpeechBubbleType::None,
277            ChatType::Tell(_u, _) => SpeechBubbleType::Tell,
278            ChatType::Say(_u) => SpeechBubbleType::Say,
279            ChatType::Group(_u, _s) => SpeechBubbleType::Group,
280            ChatType::Faction(_u, _s) => SpeechBubbleType::Faction,
281            ChatType::Region(_u) => SpeechBubbleType::Region,
282            ChatType::World(_u) => SpeechBubbleType::World,
283            ChatType::Npc(_u) => SpeechBubbleType::None,
284            ChatType::NpcSay(_u) => SpeechBubbleType::Say,
285            ChatType::NpcTell(_f, _t) => SpeechBubbleType::Say,
286            ChatType::Meta => SpeechBubbleType::None,
287        }
288    }
289
290    pub fn uid(&self) -> Option<Uid> { self.chat_type.uid() }
291
292    pub fn content(&self) -> &Content { &self.content }
293
294    pub fn into_content(self) -> Content { self.content }
295
296    pub fn set_content(&mut self, content: Content) { self.content = content; }
297}
298
299/// Player factions are used to coordinate pvp vs hostile factions or segment
300/// chat from the world
301///
302/// Factions are currently just an associated String (the faction's name)
303#[derive(Clone, Debug)]
304pub struct Faction(pub String);
305impl Component for Faction {
306    type Storage = DenseVecStorage<Self>;
307}
308impl From<String> for Faction {
309    fn from(s: String) -> Self { Faction(s) }
310}
311
312/// List of chat types for players and NPCs. Each one has its own icon.
313///
314/// This is a subset of `ChatType`, and a superset of `ChatMode`
315pub enum SpeechBubbleType {
316    // One for each chat mode
317    Tell,
318    Say,
319    Region,
320    Group,
321    Faction,
322    World,
323    // For NPCs
324    Quest, // TODO not implemented
325    Trade, // TODO not implemented
326    None,  // No icon (default for npcs)
327}
328
329/// Adds a speech bubble above the character
330pub struct SpeechBubble {
331    pub content: Content,
332    pub icon: SpeechBubbleType,
333    pub timeout: Instant,
334}
335
336impl SpeechBubble {
337    /// Default duration in seconds of speech bubbles
338    pub const DEFAULT_DURATION: f64 = 5.0;
339
340    pub fn new(content: Content, icon: SpeechBubbleType) -> Self {
341        let timeout = Instant::now() + Duration::from_secs_f64(SpeechBubble::DEFAULT_DURATION);
342        Self {
343            content,
344            icon,
345            timeout,
346        }
347    }
348
349    pub fn content(&self) -> &Content { &self.content }
350}