2021-01-18 05:46:53 +00:00
use crate ::{
comp ::{ group ::Group , BuffKind } ,
uid ::Uid ,
} ;
2023-04-11 14:46:36 +00:00
use hashbrown ::HashMap ;
2020-07-06 14:23:08 +00:00
use serde ::{ Deserialize , Serialize } ;
2022-08-08 04:38:20 +00:00
use specs ::{ Component , DenseVecStorage } ;
2020-06-04 07:11:35 +00:00
use std ::time ::{ Duration , Instant } ;
2020-06-01 04:33:39 +00:00
2020-06-12 07:43:20 +00:00
/// A player's current chat mode. These are chat types that can only be sent by
/// the player.
2021-02-10 19:42:59 +00:00
#[ derive(Clone, Debug, Serialize, Deserialize) ]
2020-06-01 04:33:39 +00:00
pub enum ChatMode {
2020-06-02 02:42:26 +00:00
/// Private message to another player (by uuid)
Tell ( Uid ) ,
2020-06-01 04:33:39 +00:00
/// Talk to players within shouting distance
Say ,
/// Talk to players in your region of the world
Region ,
/// Talk to your current group of players
2020-07-12 20:18:57 +00:00
Group ( Group ) ,
2020-06-01 04:33:39 +00:00
/// Talk to your faction
2020-06-04 09:40:05 +00:00
Faction ( String ) ,
2020-06-01 04:33:39 +00:00
/// Talk to every player on the server
World ,
}
2020-06-04 07:11:35 +00:00
2020-06-01 04:33:39 +00:00
impl Component for ChatMode {
2022-08-08 04:38:20 +00:00
type Storage = DenseVecStorage < Self > ;
2020-06-01 04:33:39 +00:00
}
2020-06-04 07:11:35 +00:00
impl ChatMode {
2023-04-11 14:46:36 +00:00
/// Create a plain message from your current chat mode and uuid.
pub fn to_plain_msg ( & self , from : Uid , text : impl ToString ) -> UnresolvedChatMsg {
2020-06-04 07:11:35 +00:00
let chat_type = match self {
ChatMode ::Tell ( to ) = > ChatType ::Tell ( from , * to ) ,
ChatMode ::Say = > ChatType ::Say ( from ) ,
ChatMode ::Region = > ChatType ::Region ( from ) ,
2020-07-12 20:18:57 +00:00
ChatMode ::Group ( group ) = > ChatType ::Group ( from , * group ) ,
ChatMode ::Faction ( faction ) = > ChatType ::Faction ( from , faction . clone ( ) ) ,
2020-06-04 07:11:35 +00:00
ChatMode ::World = > ChatType ::World ( from ) ,
} ;
2023-04-11 14:46:36 +00:00
UnresolvedChatMsg {
chat_type ,
content : Content ::Plain ( text . to_string ( ) ) ,
}
2020-06-04 07:11:35 +00:00
}
}
2021-04-09 07:34:58 +00:00
impl ChatMode {
pub const fn default ( ) -> Self { Self ::World }
2020-06-04 09:40:05 +00:00
}
2020-09-06 19:42:32 +00:00
#[ derive(Debug, Clone, Serialize, Deserialize) ]
pub enum KillType {
2021-01-18 05:46:53 +00:00
Buff ( BuffKind ) ,
2020-09-06 19:42:32 +00:00
Melee ,
Projectile ,
2020-09-09 20:26:20 +00:00
Explosion ,
2020-09-13 05:32:50 +00:00
Energy ,
2020-11-05 01:21:42 +00:00
Other ,
2020-09-06 19:42:32 +00:00
// Projectile(String), TODO: add projectile name when available
}
#[ derive(Debug, Clone, Serialize, Deserialize) ]
pub enum KillSource {
Player ( Uid , KillType ) ,
NonPlayer ( String , KillType ) ,
2021-01-18 05:46:53 +00:00
NonExistent ( KillType ) ,
2020-09-06 19:42:32 +00:00
Environment ( String ) ,
FallDamage ,
Suicide ,
Other ,
}
2020-06-12 07:43:20 +00:00
/// List of chat types. Each one is colored differently and has its own icon.
///
/// This is a superset of `SpeechBubbleType`, which is a superset of `ChatMode`
2020-06-04 09:40:05 +00:00
#[ derive(Debug, Clone, Serialize, Deserialize) ]
2020-07-12 20:18:57 +00:00
pub enum ChatType < G > {
2020-06-12 07:43:20 +00:00
/// A player came online
2020-09-06 19:42:32 +00:00
Online ( Uid ) ,
2020-06-12 07:43:20 +00:00
/// A player went offline
2020-09-06 19:42:32 +00:00
Offline ( Uid ) ,
2020-06-12 07:43:20 +00:00
/// The result of chat commands
CommandInfo ,
/// A chat command failed
CommandError ,
2020-09-06 19:42:32 +00:00
/// Inform players that someone died (Source, Victim) Source may be None
/// (ex: fall damage)
Kill ( KillSource , Uid ) ,
2020-06-12 07:43:20 +00:00
/// Server notifications to a group, such as player join/leave
2020-07-12 20:18:57 +00:00
GroupMeta ( G ) ,
2020-06-12 07:43:20 +00:00
/// Server notifications to a faction, such as player join/leave
2020-06-12 17:44:29 +00:00
FactionMeta ( String ) ,
2020-06-02 02:42:26 +00:00
/// One-on-one chat (from, to)
Tell ( Uid , Uid ) ,
/// Chat with nearby players
Say ( Uid ) ,
/// Group chat
2020-07-12 20:18:57 +00:00
Group ( Uid , G ) ,
2020-06-02 02:42:26 +00:00
/// Factional chat
2020-06-04 09:40:05 +00:00
Faction ( Uid , String ) ,
2020-06-02 02:42:26 +00:00
/// Regional chat
Region ( Uid ) ,
/// World chat
World ( Uid ) ,
2020-06-04 07:11:35 +00:00
/// Messages sent from NPCs (Not shown in chat but as speech bubbles)
2023-04-11 14:46:36 +00:00
Npc ( Uid ) ,
2021-03-16 01:30:35 +00:00
/// From NPCs but in the chat for clients in the near vicinity
2023-04-11 14:46:36 +00:00
NpcSay ( Uid ) ,
2021-05-11 17:26:22 +00:00
/// From NPCs but in the chat for a specific client. Shows a chat bubble.
/// (from, to, localization variant)
2023-04-11 14:46:36 +00:00
NpcTell ( Uid , Uid ) ,
2020-06-28 17:10:01 +00:00
/// Anything else
Meta ,
2020-06-02 02:42:26 +00:00
}
2020-07-12 20:18:57 +00:00
impl < G > ChatType < G > {
2023-04-11 14:46:36 +00:00
pub fn into_plain_msg ( self , text : impl ToString ) -> GenericChatMsg < G > {
2020-07-12 20:18:57 +00:00
GenericChatMsg {
2020-06-12 07:43:20 +00:00
chat_type : self ,
2023-04-11 14:46:36 +00:00
content : Content ::Plain ( text . to_string ( ) ) ,
}
}
pub fn into_msg ( self , content : Content ) -> GenericChatMsg < G > {
GenericChatMsg {
chat_type : self ,
content ,
2020-06-12 17:44:29 +00:00
}
}
2022-08-07 18:50:51 +00:00
pub fn uid ( & self ) -> Option < Uid > {
match self {
ChatType ::Online ( _ ) = > None ,
ChatType ::Offline ( _ ) = > None ,
ChatType ::CommandInfo = > None ,
ChatType ::CommandError = > None ,
ChatType ::FactionMeta ( _ ) = > None ,
ChatType ::GroupMeta ( _ ) = > None ,
ChatType ::Kill ( _ , _ ) = > None ,
ChatType ::Tell ( u , _t ) = > Some ( * u ) ,
ChatType ::Say ( u ) = > Some ( * u ) ,
ChatType ::Group ( u , _s ) = > Some ( * u ) ,
ChatType ::Faction ( u , _s ) = > Some ( * u ) ,
ChatType ::Region ( u ) = > Some ( * u ) ,
ChatType ::World ( u ) = > Some ( * u ) ,
2023-04-11 14:46:36 +00:00
ChatType ::Npc ( u ) = > Some ( * u ) ,
ChatType ::NpcSay ( u ) = > Some ( * u ) ,
ChatType ::NpcTell ( u , _t ) = > Some ( * u ) ,
2022-08-07 18:50:51 +00:00
ChatType ::Meta = > None ,
}
}
2022-08-23 09:03:06 +00:00
/// `None` means that the chat type is automated.
pub fn is_private ( & self ) -> Option < bool > {
match self {
ChatType ::Online ( _ )
| ChatType ::Offline ( _ )
| ChatType ::CommandInfo
| ChatType ::CommandError
| ChatType ::FactionMeta ( _ )
| ChatType ::GroupMeta ( _ )
2023-04-11 14:46:36 +00:00
| ChatType ::Npc ( _ )
| ChatType ::NpcSay ( _ )
| ChatType ::NpcTell ( _ , _ )
2022-08-23 09:03:06 +00:00
| ChatType ::Meta
| ChatType ::Kill ( _ , _ ) = > None ,
ChatType ::Tell ( _ , _ ) | ChatType ::Group ( _ , _ ) | ChatType ::Faction ( _ , _ ) = > Some ( true ) ,
ChatType ::Say ( _ ) | ChatType ::Region ( _ ) | ChatType ::World ( _ ) = > Some ( false ) ,
}
}
2020-07-12 20:18:57 +00:00
}
2020-12-13 17:11:55 +00:00
2023-04-11 14:46:36 +00:00
/// The content of a chat message.
// TODO: This could be generalised to *any* in-game text, not just chat messages (hence it not being
// called `ChatContent`). A few examples:
//
// - Signposts, both those appearing as overhead messages and those displayed 'in-world' on a shop
// sign
// - UI elements
// - In-game notes/books (we could add a variant that allows structuring complex, novel textual
// information as a syntax tree or some other intermediate format that can be localised by the
// client)
2023-04-11 22:51:07 +00:00
// TODO: We probably want to have this type be able to represent similar things to
// `fluent::FluentValue`, such as numeric values, so that they can be properly localised in whatever
// manner is required.
2023-07-09 20:03:09 +00:00
#[ derive(Debug, Clone, Serialize, Deserialize, PartialEq) ]
2023-04-11 14:46:36 +00:00
pub enum Content {
/// The content is a plaintext string that should be shown to the user
/// verbatim.
Plain ( String ) ,
/// The content is a localizable message with the given arguments.
Localized {
/// i18n key
key : String ,
/// Pseudorandom seed value that allows frontends to select a
/// deterministic (but pseudorandom) localised output
2023-07-09 20:03:09 +00:00
#[ serde(default = " random_seed " ) ]
2023-04-11 14:46:36 +00:00
seed : u16 ,
/// i18n arguments
2023-07-09 20:03:09 +00:00
#[ serde(default) ]
2023-04-12 09:34:24 +00:00
args : HashMap < String , LocalizationArg > ,
2023-04-11 14:46:36 +00:00
} ,
}
2023-04-12 09:34:24 +00:00
// TODO: Remove impl and make use of `Plain(...)` explicit (to discourage it)
2023-04-11 14:46:36 +00:00
impl From < String > for Content {
fn from ( text : String ) -> Self { Self ::Plain ( text ) }
}
2023-04-12 09:34:24 +00:00
// TODO: Remove impl and make use of `Plain(...)` explicit (to discourage it)
2023-04-11 14:46:36 +00:00
impl < ' a > From < & ' a str > for Content {
fn from ( text : & ' a str ) -> Self { Self ::Plain ( text . to_string ( ) ) }
}
2023-04-12 09:34:24 +00:00
/// A localisation argument for localised content (see [`Content::Localized`]).
2023-07-09 20:03:09 +00:00
#[ derive(Debug, Clone, Serialize, Deserialize, PartialEq) ]
2023-04-12 09:34:24 +00:00
pub enum LocalizationArg {
/// The localisation argument is itself a section of content.
///
/// Note that this allows [`Content`] to recursively refer to itself. It may
/// be tempting to decide to parameterise everything, having dialogue
/// generated with a compact tree. "It's simpler!", you might say. False.
/// Over-parameterisation is an anti-pattern that hurts translators. Where
/// possible, prefer fewer levels of nesting unless doing so would
/// result in an intractably larger number of combinations. See [here](https://github.com/projectfluent/fluent/wiki/Good-Practices-for-Developers#prefer-wet-over-dry) for the
/// guidance provided by the docs for `fluent`, the localisation library
/// used by clients.
Content ( Content ) ,
/// The localisation argument is a natural number
Nat ( u64 ) ,
}
2023-04-13 13:34:31 +00:00
impl From < Content > for LocalizationArg {
fn from ( content : Content ) -> Self { Self ::Content ( content ) }
}
2023-04-12 09:34:24 +00:00
// TODO: Remove impl and make use of `Content(Plain(...))` explicit (to
// discourage it)
impl From < String > for LocalizationArg {
fn from ( text : String ) -> Self { Self ::Content ( Content ::Plain ( text ) ) }
}
// TODO: Remove impl and make use of `Content(Plain(...))` explicit (to
// discourage it)
impl < ' a > From < & ' a str > for LocalizationArg {
fn from ( text : & ' a str ) -> Self { Self ::Content ( Content ::Plain ( text . to_string ( ) ) ) }
}
// TODO: Remove impl and make use of `Content(Plain(...))` explicit (to
// discourage it)
impl From < u64 > for LocalizationArg {
fn from ( n : u64 ) -> Self { Self ::Nat ( n ) }
}
2023-07-09 20:03:09 +00:00
fn random_seed ( ) -> u16 { rand ::random ( ) }
2023-04-11 14:46:36 +00:00
impl Content {
pub fn localized ( key : impl ToString ) -> Self {
Self ::Localized {
key : key . to_string ( ) ,
2023-07-09 20:03:09 +00:00
seed : random_seed ( ) ,
2023-04-11 14:46:36 +00:00
args : HashMap ::default ( ) ,
}
}
2023-04-12 09:34:24 +00:00
pub fn localized_with_args < ' a , A : Into < LocalizationArg > > (
2023-04-11 14:46:36 +00:00
key : impl ToString ,
2023-04-11 22:18:34 +00:00
args : impl IntoIterator < Item = ( & ' a str , A ) > ,
2023-04-11 14:46:36 +00:00
) -> Self {
Self ::Localized {
key : key . to_string ( ) ,
2023-04-11 16:00:08 +00:00
seed : rand ::random ( ) ,
2023-04-11 14:46:36 +00:00
args : args
. into_iter ( )
2023-04-11 22:18:34 +00:00
. map ( | ( k , v ) | ( k . to_string ( ) , v . into ( ) ) )
2023-04-11 14:46:36 +00:00
. collect ( ) ,
}
}
pub fn as_plain ( & self ) -> Option < & str > {
match self {
Self ::Plain ( text ) = > Some ( text . as_str ( ) ) ,
Self ::Localized { .. } = > None ,
}
}
}
2020-09-06 19:42:32 +00:00
// Stores chat text, type
2020-06-02 02:42:26 +00:00
#[ derive(Debug, Clone, Serialize, Deserialize) ]
2020-07-12 20:18:57 +00:00
pub struct GenericChatMsg < G > {
pub chat_type : ChatType < G > ,
2023-04-11 14:46:36 +00:00
content : Content ,
2020-06-02 02:42:26 +00:00
}
2020-07-12 20:18:57 +00:00
pub type ChatMsg = GenericChatMsg < String > ;
pub type UnresolvedChatMsg = GenericChatMsg < Group > ;
impl < G > GenericChatMsg < G > {
2020-06-05 01:48:26 +00:00
pub const NPC_DISTANCE : f32 = 100.0 ;
2021-03-16 01:30:35 +00:00
pub const NPC_SAY_DISTANCE : f32 = 30.0 ;
2020-06-05 01:48:26 +00:00
pub const REGION_DISTANCE : f32 = 1000.0 ;
pub const SAY_DISTANCE : f32 = 100.0 ;
2023-04-11 14:46:36 +00:00
pub fn npc ( uid : Uid , content : Content ) -> Self {
let chat_type = ChatType ::Npc ( uid ) ;
Self { chat_type , content }
2020-06-04 07:11:35 +00:00
}
2023-04-11 14:46:36 +00:00
pub fn npc_say ( uid : Uid , content : Content ) -> Self {
let chat_type = ChatType ::NpcSay ( uid ) ;
Self { chat_type , content }
2021-03-16 01:30:35 +00:00
}
2023-04-11 14:46:36 +00:00
pub fn npc_tell ( from : Uid , to : Uid , content : Content ) -> Self {
let chat_type = ChatType ::NpcTell ( from , to ) ;
Self { chat_type , content }
2021-05-11 17:26:22 +00:00
}
2020-07-12 20:18:57 +00:00
pub fn map_group < T > ( self , mut f : impl FnMut ( G ) -> T ) -> GenericChatMsg < T > {
let chat_type = match self . chat_type {
2020-09-06 19:42:32 +00:00
ChatType ::Online ( a ) = > ChatType ::Online ( a ) ,
ChatType ::Offline ( a ) = > ChatType ::Offline ( a ) ,
2020-07-12 20:18:57 +00:00
ChatType ::CommandInfo = > ChatType ::CommandInfo ,
ChatType ::CommandError = > ChatType ::CommandError ,
ChatType ::FactionMeta ( a ) = > ChatType ::FactionMeta ( a ) ,
ChatType ::GroupMeta ( g ) = > ChatType ::GroupMeta ( f ( g ) ) ,
2020-09-06 19:42:32 +00:00
ChatType ::Kill ( a , b ) = > ChatType ::Kill ( a , b ) ,
2020-07-12 20:18:57 +00:00
ChatType ::Tell ( a , b ) = > ChatType ::Tell ( a , b ) ,
ChatType ::Say ( a ) = > ChatType ::Say ( a ) ,
ChatType ::Group ( a , g ) = > ChatType ::Group ( a , f ( g ) ) ,
ChatType ::Faction ( a , b ) = > ChatType ::Faction ( a , b ) ,
ChatType ::Region ( a ) = > ChatType ::Region ( a ) ,
ChatType ::World ( a ) = > ChatType ::World ( a ) ,
2023-04-11 14:46:36 +00:00
ChatType ::Npc ( a ) = > ChatType ::Npc ( a ) ,
ChatType ::NpcSay ( a ) = > ChatType ::NpcSay ( a ) ,
ChatType ::NpcTell ( a , b ) = > ChatType ::NpcTell ( a , b ) ,
2020-07-12 20:18:57 +00:00
ChatType ::Meta = > ChatType ::Meta ,
} ;
GenericChatMsg {
chat_type ,
2023-04-11 14:46:36 +00:00
content : self . content ,
2020-07-12 20:18:57 +00:00
}
}
2021-03-27 16:40:43 +00:00
pub fn get_group ( & self ) -> Option < & G > {
match & self . chat_type {
2021-07-11 18:41:52 +00:00
ChatType ::GroupMeta ( g ) = > Some ( g ) ,
ChatType ::Group ( _ , g ) = > Some ( g ) ,
2021-03-27 16:40:43 +00:00
_ = > None ,
}
}
2020-06-04 07:11:35 +00:00
pub fn to_bubble ( & self ) -> Option < ( SpeechBubble , Uid ) > {
2023-04-11 14:46:36 +00:00
self . uid ( )
. map ( | from | ( SpeechBubble ::new ( self . content . clone ( ) , self . icon ( ) ) , from ) )
2020-06-05 22:36:31 +00:00
}
2020-06-11 18:25:29 +00:00
pub fn icon ( & self ) -> SpeechBubbleType {
2020-06-05 22:36:31 +00:00
match & self . chat_type {
2020-09-06 19:42:32 +00:00
ChatType ::Online ( _ ) = > SpeechBubbleType ::None ,
ChatType ::Offline ( _ ) = > SpeechBubbleType ::None ,
2020-06-12 07:43:20 +00:00
ChatType ::CommandInfo = > SpeechBubbleType ::None ,
ChatType ::CommandError = > SpeechBubbleType ::None ,
2020-06-12 17:44:29 +00:00
ChatType ::FactionMeta ( _ ) = > SpeechBubbleType ::None ,
ChatType ::GroupMeta ( _ ) = > SpeechBubbleType ::None ,
2020-09-06 19:42:32 +00:00
ChatType ::Kill ( _ , _ ) = > SpeechBubbleType ::None ,
2020-06-11 22:39:57 +00:00
ChatType ::Tell ( _u , _ ) = > SpeechBubbleType ::Tell ,
ChatType ::Say ( _u ) = > SpeechBubbleType ::Say ,
ChatType ::Group ( _u , _s ) = > SpeechBubbleType ::Group ,
ChatType ::Faction ( _u , _s ) = > SpeechBubbleType ::Faction ,
ChatType ::Region ( _u ) = > SpeechBubbleType ::Region ,
ChatType ::World ( _u ) = > SpeechBubbleType ::World ,
2023-04-11 14:46:36 +00:00
ChatType ::Npc ( _u ) = > SpeechBubbleType ::None ,
ChatType ::NpcSay ( _u ) = > SpeechBubbleType ::Say ,
ChatType ::NpcTell ( _f , _t ) = > SpeechBubbleType ::Say ,
2020-06-28 17:10:01 +00:00
ChatType ::Meta = > SpeechBubbleType ::None ,
2020-06-05 22:36:31 +00:00
}
2020-06-02 02:42:26 +00:00
}
2020-06-05 01:48:26 +00:00
2022-08-07 18:50:51 +00:00
pub fn uid ( & self ) -> Option < Uid > { self . chat_type . uid ( ) }
2023-04-11 14:46:36 +00:00
pub fn content ( & self ) -> & Content { & self . content }
2023-04-12 09:34:24 +00:00
pub fn into_content ( self ) -> Content { self . content }
2023-04-11 14:46:36 +00:00
pub fn set_content ( & mut self , content : Content ) { self . content = content ; }
2020-06-02 02:42:26 +00:00
}
2020-06-01 04:33:39 +00:00
/// Player factions are used to coordinate pvp vs hostile factions or segment
/// chat from the world
///
/// Factions are currently just an associated String (the faction's name)
2020-06-04 09:40:05 +00:00
#[ derive(Clone, Debug) ]
pub struct Faction ( pub String ) ;
2020-06-01 04:33:39 +00:00
impl Component for Faction {
2022-08-08 04:38:20 +00:00
type Storage = DenseVecStorage < Self > ;
2020-06-01 04:33:39 +00:00
}
2020-06-04 09:40:05 +00:00
impl From < String > for Faction {
fn from ( s : String ) -> Self { Faction ( s ) }
}
2020-06-04 07:11:35 +00:00
2020-06-12 07:43:20 +00:00
/// List of chat types for players and NPCs. Each one has its own icon.
///
/// This is a subset of `ChatType`, and a superset of `ChatMode`
2020-06-11 18:25:29 +00:00
pub enum SpeechBubbleType {
2020-06-04 07:11:35 +00:00
// One for each chat mode
Tell ,
Say ,
Region ,
Group ,
Faction ,
World ,
// For NPCs
Quest , // TODO not implemented
Trade , // TODO not implemented
None , // No icon (default for npcs)
}
/// Adds a speech bubble above the character
pub struct SpeechBubble {
2023-04-11 14:46:36 +00:00
pub content : Content ,
2020-06-11 18:25:29 +00:00
pub icon : SpeechBubbleType ,
2020-06-04 07:11:35 +00:00
pub timeout : Instant ,
}
impl SpeechBubble {
/// Default duration in seconds of speech bubbles
pub const DEFAULT_DURATION : f64 = 5.0 ;
2023-04-11 14:46:36 +00:00
pub fn new ( content : Content , icon : SpeechBubbleType ) -> Self {
2020-06-04 07:11:35 +00:00
let timeout = Instant ::now ( ) + Duration ::from_secs_f64 ( SpeechBubble ::DEFAULT_DURATION ) ;
Self {
2023-04-11 14:46:36 +00:00
content ,
2020-06-04 07:11:35 +00:00
icon ,
2021-03-12 11:10:42 +00:00
timeout ,
2020-06-04 07:11:35 +00:00
}
}
2023-04-11 14:46:36 +00:00
pub fn content ( & self ) -> & Content { & self . content }
2020-06-04 07:11:35 +00:00
}