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    /// If this is from an entity, returns the `Uid` of that entity.
148    pub fn uid(&self) -> Option<Uid> {
149        match self {
150            ChatType::Online(_) => None,
151            ChatType::Offline(_) => None,
152            ChatType::CommandInfo => None,
153            ChatType::CommandError => None,
154            ChatType::FactionMeta(_) => None,
155            ChatType::GroupMeta(_) => None,
156            ChatType::Kill(_, _) => None,
157            ChatType::Tell(u, _t) => Some(*u),
158            ChatType::Say(u) => Some(*u),
159            ChatType::Group(u, _s) => Some(*u),
160            ChatType::Faction(u, _s) => Some(*u),
161            ChatType::Region(u) => Some(*u),
162            ChatType::World(u) => Some(*u),
163            ChatType::Npc(u) => Some(*u),
164            ChatType::NpcSay(u) => Some(*u),
165            ChatType::NpcTell(u, _t) => Some(*u),
166            ChatType::Meta => None,
167        }
168    }
169
170    /// `None` means that the chat type is automated.
171    pub fn is_private(&self) -> Option<bool> {
172        match self {
173            ChatType::Online(_)
174            | ChatType::Offline(_)
175            | ChatType::CommandInfo
176            | ChatType::CommandError
177            | ChatType::FactionMeta(_)
178            | ChatType::GroupMeta(_)
179            | ChatType::Npc(_)
180            | ChatType::NpcSay(_)
181            | ChatType::NpcTell(_, _)
182            | ChatType::Meta
183            | ChatType::Kill(_, _) => None,
184            ChatType::Tell(_, _) | ChatType::Group(_, _) | ChatType::Faction(_, _) => Some(true),
185            ChatType::Say(_) | ChatType::Region(_) | ChatType::World(_) => Some(false),
186        }
187    }
188}
189
190// Stores chat text, type
191#[derive(Debug, Clone, Serialize, Deserialize)]
192pub struct GenericChatMsg<G> {
193    pub chat_type: ChatType<G>,
194    content: Content,
195}
196
197pub type ChatMsg = GenericChatMsg<String>;
198pub type UnresolvedChatMsg = GenericChatMsg<Group>;
199
200impl<G> GenericChatMsg<G> {
201    pub const MAX_BYTES_PLAYER_CHAT_MSG: usize = 256;
202    pub const NPC_DISTANCE: f32 = 100.0;
203    pub const NPC_SAY_DISTANCE: f32 = 30.0;
204    pub const REGION_DISTANCE: f32 = 1000.0;
205    pub const SAY_DISTANCE: f32 = 100.0;
206
207    pub fn npc(uid: Uid, content: Content) -> Self {
208        let chat_type = ChatType::Npc(uid);
209        Self { chat_type, content }
210    }
211
212    pub fn npc_say(uid: Uid, content: Content) -> Self {
213        let chat_type = ChatType::NpcSay(uid);
214        Self { chat_type, content }
215    }
216
217    pub fn npc_tell(from: Uid, to: Uid, content: Content) -> Self {
218        let chat_type = ChatType::NpcTell(from, to);
219        Self { chat_type, content }
220    }
221
222    pub fn death(kill_source: KillSource, victim: Uid) -> Self {
223        Self {
224            chat_type: ChatType::Kill(kill_source, victim),
225            content: Content::Plain(String::new()),
226        }
227    }
228
229    pub fn map_group<T>(self, mut f: impl FnMut(G) -> T) -> GenericChatMsg<T> {
230        let chat_type = match self.chat_type {
231            ChatType::Online(a) => ChatType::Online(a),
232            ChatType::Offline(a) => ChatType::Offline(a),
233            ChatType::CommandInfo => ChatType::CommandInfo,
234            ChatType::CommandError => ChatType::CommandError,
235            ChatType::FactionMeta(a) => ChatType::FactionMeta(a),
236            ChatType::GroupMeta(g) => ChatType::GroupMeta(f(g)),
237            ChatType::Kill(a, b) => ChatType::Kill(a, b),
238            ChatType::Tell(a, b) => ChatType::Tell(a, b),
239            ChatType::Say(a) => ChatType::Say(a),
240            ChatType::Group(a, g) => ChatType::Group(a, f(g)),
241            ChatType::Faction(a, b) => ChatType::Faction(a, b),
242            ChatType::Region(a) => ChatType::Region(a),
243            ChatType::World(a) => ChatType::World(a),
244            ChatType::Npc(a) => ChatType::Npc(a),
245            ChatType::NpcSay(a) => ChatType::NpcSay(a),
246            ChatType::NpcTell(a, b) => ChatType::NpcTell(a, b),
247            ChatType::Meta => ChatType::Meta,
248        };
249
250        GenericChatMsg {
251            chat_type,
252            content: self.content,
253        }
254    }
255
256    pub fn get_group(&self) -> Option<&G> {
257        match &self.chat_type {
258            ChatType::GroupMeta(g) => Some(g),
259            ChatType::Group(_, g) => Some(g),
260            _ => None,
261        }
262    }
263
264    pub fn to_bubble(&self) -> Option<(SpeechBubble, Uid)> {
265        self.uid()
266            .map(|from| (SpeechBubble::new(self.content.clone(), self.icon()), from))
267    }
268
269    pub fn icon(&self) -> SpeechBubbleType {
270        match &self.chat_type {
271            ChatType::Online(_) => SpeechBubbleType::None,
272            ChatType::Offline(_) => SpeechBubbleType::None,
273            ChatType::CommandInfo => SpeechBubbleType::None,
274            ChatType::CommandError => SpeechBubbleType::None,
275            ChatType::FactionMeta(_) => SpeechBubbleType::None,
276            ChatType::GroupMeta(_) => SpeechBubbleType::None,
277            ChatType::Kill(_, _) => SpeechBubbleType::None,
278            ChatType::Tell(_u, _) => SpeechBubbleType::Tell,
279            ChatType::Say(_u) => SpeechBubbleType::Say,
280            ChatType::Group(_u, _s) => SpeechBubbleType::Group,
281            ChatType::Faction(_u, _s) => SpeechBubbleType::Faction,
282            ChatType::Region(_u) => SpeechBubbleType::Region,
283            ChatType::World(_u) => SpeechBubbleType::World,
284            ChatType::Npc(_u) => SpeechBubbleType::None,
285            ChatType::NpcSay(_u) => SpeechBubbleType::Say,
286            ChatType::NpcTell(_f, _t) => SpeechBubbleType::Say,
287            ChatType::Meta => SpeechBubbleType::None,
288        }
289    }
290
291    /// If this is from an entity, returns the `Uid` of that entity.
292    pub fn uid(&self) -> Option<Uid> { self.chat_type.uid() }
293
294    pub fn content(&self) -> &Content { &self.content }
295
296    pub fn into_content(self) -> Content { self.content }
297
298    pub fn set_content(&mut self, content: Content) { self.content = content; }
299}
300
301/// Player factions are used to coordinate pvp vs hostile factions or segment
302/// chat from the world
303///
304/// Factions are currently just an associated String (the faction's name)
305#[derive(Clone, Debug)]
306pub struct Faction(pub String);
307impl Component for Faction {
308    type Storage = DenseVecStorage<Self>;
309}
310impl From<String> for Faction {
311    fn from(s: String) -> Self { Faction(s) }
312}
313
314/// List of chat types for players and NPCs. Each one has its own icon.
315///
316/// This is a subset of `ChatType`, and a superset of `ChatMode`
317pub enum SpeechBubbleType {
318    // One for each chat mode
319    Tell,
320    Say,
321    Region,
322    Group,
323    Faction,
324    World,
325    // For NPCs
326    Quest, // TODO not implemented
327    Trade, // TODO not implemented
328    None,  // No icon (default for npcs)
329}
330
331/// Adds a speech bubble above the character
332pub struct SpeechBubble {
333    pub content: Content,
334    pub icon: SpeechBubbleType,
335    pub timeout: Instant,
336}
337
338impl SpeechBubble {
339    /// Default duration in seconds of speech bubbles
340    pub const DEFAULT_DURATION: f64 = 5.0;
341
342    pub fn new(content: Content, icon: SpeechBubbleType) -> Self {
343        let timeout = Instant::now() + Duration::from_secs_f64(SpeechBubble::DEFAULT_DURATION);
344        Self {
345            content,
346            icon,
347            timeout,
348        }
349    }
350
351    pub fn content(&self) -> &Content { &self.content }
352}