mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Overhauled chat message representation to allow for more exhaustive localisation
This commit is contained in:
parent
bc4d1a71f6
commit
edcc2f1870
1
Cargo.lock
generated
1
Cargo.lock
generated
@ -7173,6 +7173,7 @@ dependencies = [
|
|||||||
name = "veloren-voxygen-i18n-helpers"
|
name = "veloren-voxygen-i18n-helpers"
|
||||||
version = "0.10.0"
|
version = "0.10.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"hashbrown 0.12.3",
|
||||||
"tracing",
|
"tracing",
|
||||||
"veloren-client-i18n",
|
"veloren-client-i18n",
|
||||||
"veloren-common",
|
"veloren-common",
|
||||||
|
@ -2280,13 +2280,15 @@ impl Client {
|
|||||||
.any(|r| !matches!(r, group::Role::Pet))
|
.any(|r| !matches!(r, group::Role::Pet))
|
||||||
{
|
{
|
||||||
frontend_events
|
frontend_events
|
||||||
.push(Event::Chat(comp::ChatType::Meta.chat_msg(
|
// TODO: localise
|
||||||
|
.push(Event::Chat(comp::ChatType::Meta.into_plain_msg(
|
||||||
"Type /g or /group to chat with your group members",
|
"Type /g or /group to chat with your group members",
|
||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
if let Some(player_info) = self.player_list.get(&uid) {
|
if let Some(player_info) = self.player_list.get(&uid) {
|
||||||
frontend_events.push(Event::Chat(
|
frontend_events.push(Event::Chat(
|
||||||
comp::ChatType::GroupMeta("Group".into()).chat_msg(format!(
|
// TODO: localise
|
||||||
|
comp::ChatType::GroupMeta("Group".into()).into_plain_msg(format!(
|
||||||
"[{}] joined group",
|
"[{}] joined group",
|
||||||
self.personalize_alias(uid, player_info.player_alias.clone())
|
self.personalize_alias(uid, player_info.player_alias.clone())
|
||||||
)),
|
)),
|
||||||
@ -2303,7 +2305,8 @@ impl Client {
|
|||||||
Removed(uid) => {
|
Removed(uid) => {
|
||||||
if let Some(player_info) = self.player_list.get(&uid) {
|
if let Some(player_info) = self.player_list.get(&uid) {
|
||||||
frontend_events.push(Event::Chat(
|
frontend_events.push(Event::Chat(
|
||||||
comp::ChatType::GroupMeta("Group".into()).chat_msg(format!(
|
// TODO: localise
|
||||||
|
comp::ChatType::GroupMeta("Group".into()).into_plain_msg(format!(
|
||||||
"[{}] left group",
|
"[{}] left group",
|
||||||
self.personalize_alias(uid, player_info.player_alias.clone())
|
self.personalize_alias(uid, player_info.player_alias.clone())
|
||||||
)),
|
)),
|
||||||
@ -2790,20 +2793,20 @@ impl Client {
|
|||||||
KillSource::Other => (),
|
KillSource::Other => (),
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
comp::ChatType::Tell(from, to) | comp::ChatType::NpcTell(from, to, _) => {
|
comp::ChatType::Tell(from, to) | comp::ChatType::NpcTell(from, to) => {
|
||||||
alias_of_uid(from);
|
alias_of_uid(from);
|
||||||
alias_of_uid(to);
|
alias_of_uid(to);
|
||||||
},
|
},
|
||||||
comp::ChatType::Say(uid)
|
comp::ChatType::Say(uid)
|
||||||
| comp::ChatType::Region(uid)
|
| comp::ChatType::Region(uid)
|
||||||
| comp::ChatType::World(uid)
|
| comp::ChatType::World(uid)
|
||||||
| comp::ChatType::NpcSay(uid, _) => {
|
| comp::ChatType::NpcSay(uid) => {
|
||||||
alias_of_uid(uid);
|
alias_of_uid(uid);
|
||||||
},
|
},
|
||||||
comp::ChatType::Group(uid, _) | comp::ChatType::Faction(uid, _) => {
|
comp::ChatType::Group(uid, _) | comp::ChatType::Faction(uid, _) => {
|
||||||
alias_of_uid(uid);
|
alias_of_uid(uid);
|
||||||
},
|
},
|
||||||
comp::ChatType::Npc(uid, _) => alias_of_uid(uid),
|
comp::ChatType::Npc(uid) => alias_of_uid(uid),
|
||||||
comp::ChatType::Meta => (),
|
comp::ChatType::Meta => (),
|
||||||
};
|
};
|
||||||
result
|
result
|
||||||
|
@ -6,7 +6,7 @@ use crate::sync;
|
|||||||
use common::{
|
use common::{
|
||||||
calendar::Calendar,
|
calendar::Calendar,
|
||||||
character::{self, CharacterItem},
|
character::{self, CharacterItem},
|
||||||
comp::{self, invite::InviteKind, item::MaterialStatManifest},
|
comp::{self, invite::InviteKind, item::MaterialStatManifest, Content},
|
||||||
event::UpdateCharacterMetadata,
|
event::UpdateCharacterMetadata,
|
||||||
lod,
|
lod,
|
||||||
outcome::Outcome,
|
outcome::Outcome,
|
||||||
@ -215,11 +215,10 @@ pub enum ServerGeneral {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl ServerGeneral {
|
impl ServerGeneral {
|
||||||
pub fn server_msg<S>(chat_type: comp::ChatType<String>, msg: S) -> Self
|
// TODO: Don't use `Into<Content>` since this treats all strings as plaintext,
|
||||||
where
|
// properly localise server messages
|
||||||
S: Into<String>,
|
pub fn server_msg(chat_type: comp::ChatType<String>, content: impl Into<Content>) -> Self {
|
||||||
{
|
ServerGeneral::ChatMsg(chat_type.into_msg(content.into()))
|
||||||
ServerGeneral::ChatMsg(chat_type.chat_msg(msg))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,6 +2,7 @@ use crate::{
|
|||||||
comp::{group::Group, BuffKind},
|
comp::{group::Group, BuffKind},
|
||||||
uid::Uid,
|
uid::Uid,
|
||||||
};
|
};
|
||||||
|
use hashbrown::HashMap;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use specs::{Component, DenseVecStorage};
|
use specs::{Component, DenseVecStorage};
|
||||||
use std::time::{Duration, Instant};
|
use std::time::{Duration, Instant};
|
||||||
@ -29,8 +30,8 @@ impl Component for ChatMode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl ChatMode {
|
impl ChatMode {
|
||||||
/// Create a message from your current chat mode and uuid.
|
/// Create a plain message from your current chat mode and uuid.
|
||||||
pub fn new_message(&self, from: Uid, message: String) -> UnresolvedChatMsg {
|
pub fn to_plain_msg(&self, from: Uid, text: impl ToString) -> UnresolvedChatMsg {
|
||||||
let chat_type = match self {
|
let chat_type = match self {
|
||||||
ChatMode::Tell(to) => ChatType::Tell(from, *to),
|
ChatMode::Tell(to) => ChatType::Tell(from, *to),
|
||||||
ChatMode::Say => ChatType::Say(from),
|
ChatMode::Say => ChatType::Say(from),
|
||||||
@ -39,7 +40,10 @@ impl ChatMode {
|
|||||||
ChatMode::Faction(faction) => ChatType::Faction(from, faction.clone()),
|
ChatMode::Faction(faction) => ChatType::Faction(from, faction.clone()),
|
||||||
ChatMode::World => ChatType::World(from),
|
ChatMode::World => ChatType::World(from),
|
||||||
};
|
};
|
||||||
UnresolvedChatMsg { chat_type, message }
|
UnresolvedChatMsg {
|
||||||
|
chat_type,
|
||||||
|
content: Content::Plain(text.to_string()),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -102,26 +106,28 @@ pub enum ChatType<G> {
|
|||||||
/// World chat
|
/// World chat
|
||||||
World(Uid),
|
World(Uid),
|
||||||
/// Messages sent from NPCs (Not shown in chat but as speech bubbles)
|
/// Messages sent from NPCs (Not shown in chat but as speech bubbles)
|
||||||
///
|
Npc(Uid),
|
||||||
/// The u16 field is a random number for selecting localization variants.
|
|
||||||
Npc(Uid, u16),
|
|
||||||
/// From NPCs but in the chat for clients in the near vicinity
|
/// From NPCs but in the chat for clients in the near vicinity
|
||||||
NpcSay(Uid, u16),
|
NpcSay(Uid),
|
||||||
/// From NPCs but in the chat for a specific client. Shows a chat bubble.
|
/// From NPCs but in the chat for a specific client. Shows a chat bubble.
|
||||||
/// (from, to, localization variant)
|
/// (from, to, localization variant)
|
||||||
NpcTell(Uid, Uid, u16),
|
NpcTell(Uid, Uid),
|
||||||
/// Anything else
|
/// Anything else
|
||||||
Meta,
|
Meta,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<G> ChatType<G> {
|
impl<G> ChatType<G> {
|
||||||
pub fn chat_msg<S>(self, msg: S) -> GenericChatMsg<G>
|
pub fn into_plain_msg(self, text: impl ToString) -> GenericChatMsg<G> {
|
||||||
where
|
|
||||||
S: Into<String>,
|
|
||||||
{
|
|
||||||
GenericChatMsg {
|
GenericChatMsg {
|
||||||
chat_type: self,
|
chat_type: self,
|
||||||
message: msg.into(),
|
content: Content::Plain(text.to_string()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn into_msg(self, content: Content) -> GenericChatMsg<G> {
|
||||||
|
GenericChatMsg {
|
||||||
|
chat_type: self,
|
||||||
|
content,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -140,9 +146,9 @@ impl<G> ChatType<G> {
|
|||||||
ChatType::Faction(u, _s) => Some(*u),
|
ChatType::Faction(u, _s) => Some(*u),
|
||||||
ChatType::Region(u) => Some(*u),
|
ChatType::Region(u) => Some(*u),
|
||||||
ChatType::World(u) => Some(*u),
|
ChatType::World(u) => Some(*u),
|
||||||
ChatType::Npc(u, _r) => Some(*u),
|
ChatType::Npc(u) => Some(*u),
|
||||||
ChatType::NpcSay(u, _r) => Some(*u),
|
ChatType::NpcSay(u) => Some(*u),
|
||||||
ChatType::NpcTell(u, _t, _r) => Some(*u),
|
ChatType::NpcTell(u, _t) => Some(*u),
|
||||||
ChatType::Meta => None,
|
ChatType::Meta => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -156,9 +162,9 @@ impl<G> ChatType<G> {
|
|||||||
| ChatType::CommandError
|
| ChatType::CommandError
|
||||||
| ChatType::FactionMeta(_)
|
| ChatType::FactionMeta(_)
|
||||||
| ChatType::GroupMeta(_)
|
| ChatType::GroupMeta(_)
|
||||||
| ChatType::Npc(_, _)
|
| ChatType::Npc(_)
|
||||||
| ChatType::NpcSay(_, _)
|
| ChatType::NpcSay(_)
|
||||||
| ChatType::NpcTell(_, _, _)
|
| ChatType::NpcTell(_, _)
|
||||||
| ChatType::Meta
|
| ChatType::Meta
|
||||||
| ChatType::Kill(_, _) => None,
|
| ChatType::Kill(_, _) => None,
|
||||||
ChatType::Tell(_, _) | ChatType::Group(_, _) | ChatType::Faction(_, _) => Some(true),
|
ChatType::Tell(_, _) | ChatType::Group(_, _) | ChatType::Faction(_, _) => Some(true),
|
||||||
@ -167,11 +173,87 @@ impl<G> ChatType<G> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 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)
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
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
|
||||||
|
seed: u16,
|
||||||
|
/// i18n arguments
|
||||||
|
args: HashMap<String, String>,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<String> for Content {
|
||||||
|
fn from(text: String) -> Self { Self::Plain(text) }
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> From<&'a str> for Content {
|
||||||
|
fn from(text: &'a str) -> Self { Self::Plain(text.to_string()) }
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Content {
|
||||||
|
pub fn localized(key: impl ToString) -> Self {
|
||||||
|
Self::Localized {
|
||||||
|
key: key.to_string(),
|
||||||
|
r: rand::random(),
|
||||||
|
args: HashMap::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn localized_with_args<'a, S: ToString>(
|
||||||
|
key: impl ToString,
|
||||||
|
args: impl IntoIterator<Item = (&'a str, S)>,
|
||||||
|
) -> Self {
|
||||||
|
Self::Localized {
|
||||||
|
key: key.to_string(),
|
||||||
|
r: rand::random(),
|
||||||
|
args: args
|
||||||
|
.into_iter()
|
||||||
|
.map(|(k, v)| (k.to_string(), v.to_string()))
|
||||||
|
.collect(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn as_plain(&self) -> Option<&str> {
|
||||||
|
match self {
|
||||||
|
Self::Plain(text) => Some(text.as_str()),
|
||||||
|
Self::Localized { .. } => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn localize<F>(&self, i18n_variation: F) -> String
|
||||||
|
where
|
||||||
|
F: Fn(&str, u16, &HashMap<String, String>) -> String,
|
||||||
|
{
|
||||||
|
match self {
|
||||||
|
Content::Plain(text) => text.to_string(),
|
||||||
|
Content::Localized { key, seed, args } => i18n_variation(key, *seed, args),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Stores chat text, type
|
// Stores chat text, type
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
pub struct GenericChatMsg<G> {
|
pub struct GenericChatMsg<G> {
|
||||||
pub chat_type: ChatType<G>,
|
pub chat_type: ChatType<G>,
|
||||||
pub message: String,
|
content: Content,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type ChatMsg = GenericChatMsg<String>;
|
pub type ChatMsg = GenericChatMsg<String>;
|
||||||
@ -183,19 +265,19 @@ impl<G> GenericChatMsg<G> {
|
|||||||
pub const REGION_DISTANCE: f32 = 1000.0;
|
pub const REGION_DISTANCE: f32 = 1000.0;
|
||||||
pub const SAY_DISTANCE: f32 = 100.0;
|
pub const SAY_DISTANCE: f32 = 100.0;
|
||||||
|
|
||||||
pub fn npc(uid: Uid, message: String) -> Self {
|
pub fn npc(uid: Uid, content: Content) -> Self {
|
||||||
let chat_type = ChatType::Npc(uid, rand::random());
|
let chat_type = ChatType::Npc(uid);
|
||||||
Self { chat_type, message }
|
Self { chat_type, content }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn npc_say(uid: Uid, message: String) -> Self {
|
pub fn npc_say(uid: Uid, content: Content) -> Self {
|
||||||
let chat_type = ChatType::NpcSay(uid, rand::random());
|
let chat_type = ChatType::NpcSay(uid);
|
||||||
Self { chat_type, message }
|
Self { chat_type, content }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn npc_tell(from: Uid, to: Uid, message: String) -> Self {
|
pub fn npc_tell(from: Uid, to: Uid, content: Content) -> Self {
|
||||||
let chat_type = ChatType::NpcTell(from, to, rand::random());
|
let chat_type = ChatType::NpcTell(from, to);
|
||||||
Self { chat_type, message }
|
Self { chat_type, content }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn map_group<T>(self, mut f: impl FnMut(G) -> T) -> GenericChatMsg<T> {
|
pub fn map_group<T>(self, mut f: impl FnMut(G) -> T) -> GenericChatMsg<T> {
|
||||||
@ -213,15 +295,15 @@ impl<G> GenericChatMsg<G> {
|
|||||||
ChatType::Faction(a, b) => ChatType::Faction(a, b),
|
ChatType::Faction(a, b) => ChatType::Faction(a, b),
|
||||||
ChatType::Region(a) => ChatType::Region(a),
|
ChatType::Region(a) => ChatType::Region(a),
|
||||||
ChatType::World(a) => ChatType::World(a),
|
ChatType::World(a) => ChatType::World(a),
|
||||||
ChatType::Npc(a, b) => ChatType::Npc(a, b),
|
ChatType::Npc(a) => ChatType::Npc(a),
|
||||||
ChatType::NpcSay(a, b) => ChatType::NpcSay(a, b),
|
ChatType::NpcSay(a) => ChatType::NpcSay(a),
|
||||||
ChatType::NpcTell(a, b, c) => ChatType::NpcTell(a, b, c),
|
ChatType::NpcTell(a, b) => ChatType::NpcTell(a, b),
|
||||||
ChatType::Meta => ChatType::Meta,
|
ChatType::Meta => ChatType::Meta,
|
||||||
};
|
};
|
||||||
|
|
||||||
GenericChatMsg {
|
GenericChatMsg {
|
||||||
chat_type,
|
chat_type,
|
||||||
message: self.message,
|
content: self.content,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -234,15 +316,8 @@ impl<G> GenericChatMsg<G> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn to_bubble(&self) -> Option<(SpeechBubble, Uid)> {
|
pub fn to_bubble(&self) -> Option<(SpeechBubble, Uid)> {
|
||||||
let icon = self.icon();
|
self.uid()
|
||||||
if let ChatType::Npc(from, r) | ChatType::NpcSay(from, r) | ChatType::NpcTell(from, _, r) =
|
.map(|from| (SpeechBubble::new(self.content.clone(), self.icon()), from))
|
||||||
self.chat_type
|
|
||||||
{
|
|
||||||
Some((SpeechBubble::npc_new(&self.message, r, icon), from))
|
|
||||||
} else {
|
|
||||||
self.uid()
|
|
||||||
.map(|from| (SpeechBubble::player_new(&self.message, icon), from))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn icon(&self) -> SpeechBubbleType {
|
pub fn icon(&self) -> SpeechBubbleType {
|
||||||
@ -260,14 +335,18 @@ impl<G> GenericChatMsg<G> {
|
|||||||
ChatType::Faction(_u, _s) => SpeechBubbleType::Faction,
|
ChatType::Faction(_u, _s) => SpeechBubbleType::Faction,
|
||||||
ChatType::Region(_u) => SpeechBubbleType::Region,
|
ChatType::Region(_u) => SpeechBubbleType::Region,
|
||||||
ChatType::World(_u) => SpeechBubbleType::World,
|
ChatType::World(_u) => SpeechBubbleType::World,
|
||||||
ChatType::Npc(_u, _r) => SpeechBubbleType::None,
|
ChatType::Npc(_u) => SpeechBubbleType::None,
|
||||||
ChatType::NpcSay(_u, _r) => SpeechBubbleType::Say,
|
ChatType::NpcSay(_u) => SpeechBubbleType::Say,
|
||||||
ChatType::NpcTell(_f, _t, _) => SpeechBubbleType::Say,
|
ChatType::NpcTell(_f, _t) => SpeechBubbleType::Say,
|
||||||
ChatType::Meta => SpeechBubbleType::None,
|
ChatType::Meta => SpeechBubbleType::None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn uid(&self) -> Option<Uid> { self.chat_type.uid() }
|
pub fn uid(&self) -> Option<Uid> { self.chat_type.uid() }
|
||||||
|
|
||||||
|
pub fn content(&self) -> &Content { &self.content }
|
||||||
|
|
||||||
|
pub fn set_content(&mut self, content: Content) { self.content = content; }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Player factions are used to coordinate pvp vs hostile factions or segment
|
/// Player factions are used to coordinate pvp vs hostile factions or segment
|
||||||
@ -283,15 +362,6 @@ impl From<String> for Faction {
|
|||||||
fn from(s: String) -> Self { Faction(s) }
|
fn from(s: String) -> Self { Faction(s) }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The contents of a speech bubble
|
|
||||||
pub enum SpeechBubbleMessage {
|
|
||||||
/// This message was said by a player and needs no translation
|
|
||||||
Plain(String),
|
|
||||||
/// This message was said by an NPC. The fields are a i18n key and a random
|
|
||||||
/// u16 index
|
|
||||||
Localized(String, u16),
|
|
||||||
}
|
|
||||||
|
|
||||||
/// List of chat types for players and NPCs. Each one has its own icon.
|
/// 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`
|
/// This is a subset of `ChatType`, and a superset of `ChatMode`
|
||||||
@ -311,7 +381,7 @@ pub enum SpeechBubbleType {
|
|||||||
|
|
||||||
/// Adds a speech bubble above the character
|
/// Adds a speech bubble above the character
|
||||||
pub struct SpeechBubble {
|
pub struct SpeechBubble {
|
||||||
pub message: SpeechBubbleMessage,
|
pub content: Content,
|
||||||
pub icon: SpeechBubbleType,
|
pub icon: SpeechBubbleType,
|
||||||
pub timeout: Instant,
|
pub timeout: Instant,
|
||||||
}
|
}
|
||||||
@ -320,33 +390,14 @@ impl SpeechBubble {
|
|||||||
/// Default duration in seconds of speech bubbles
|
/// Default duration in seconds of speech bubbles
|
||||||
pub const DEFAULT_DURATION: f64 = 5.0;
|
pub const DEFAULT_DURATION: f64 = 5.0;
|
||||||
|
|
||||||
pub fn npc_new(i18n_key: &str, r: u16, icon: SpeechBubbleType) -> Self {
|
pub fn new(content: Content, icon: SpeechBubbleType) -> Self {
|
||||||
let message = SpeechBubbleMessage::Localized(i18n_key.to_string(), r);
|
|
||||||
let timeout = Instant::now() + Duration::from_secs_f64(SpeechBubble::DEFAULT_DURATION);
|
let timeout = Instant::now() + Duration::from_secs_f64(SpeechBubble::DEFAULT_DURATION);
|
||||||
Self {
|
Self {
|
||||||
message,
|
content,
|
||||||
icon,
|
icon,
|
||||||
timeout,
|
timeout,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn player_new(message: &str, icon: SpeechBubbleType) -> Self {
|
pub fn content(&self) -> &Content { &self.content }
|
||||||
let message = SpeechBubbleMessage::Plain(message.to_string());
|
|
||||||
let timeout = Instant::now() + Duration::from_secs_f64(SpeechBubble::DEFAULT_DURATION);
|
|
||||||
Self {
|
|
||||||
message,
|
|
||||||
icon,
|
|
||||||
timeout,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn message<F>(&self, i18n_variation: F) -> String
|
|
||||||
where
|
|
||||||
F: Fn(&str, u16) -> String,
|
|
||||||
{
|
|
||||||
match &self.message {
|
|
||||||
SpeechBubbleMessage::Plain(m) => m.to_string(),
|
|
||||||
SpeechBubbleMessage::Localized(k, i) => i18n_variation(k, *i),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -75,7 +75,8 @@ pub use self::{
|
|||||||
},
|
},
|
||||||
character_state::{CharacterActivity, CharacterState, StateUpdate},
|
character_state::{CharacterActivity, CharacterState, StateUpdate},
|
||||||
chat::{
|
chat::{
|
||||||
ChatMode, ChatMsg, ChatType, Faction, SpeechBubble, SpeechBubbleType, UnresolvedChatMsg,
|
ChatMode, ChatMsg, ChatType, Content, Faction, SpeechBubble, SpeechBubbleType,
|
||||||
|
UnresolvedChatMsg,
|
||||||
},
|
},
|
||||||
combo::Combo,
|
combo::Combo,
|
||||||
controller::{
|
controller::{
|
||||||
|
@ -88,7 +88,7 @@ fn on_death(ctx: EventCtx<SimulateNpcs, OnDeath>) {
|
|||||||
);
|
);
|
||||||
Some((npc_id, site_id))
|
Some((npc_id, site_id))
|
||||||
} else {
|
} else {
|
||||||
warn!("No site found for respawning humaniod");
|
warn!("No site found for respawning humanoid");
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -22,7 +22,7 @@ use common::{
|
|||||||
},
|
},
|
||||||
item_drop,
|
item_drop,
|
||||||
projectile::ProjectileConstructor,
|
projectile::ProjectileConstructor,
|
||||||
Agent, Alignment, Body, CharacterState, ControlAction, ControlEvent, Controller,
|
Agent, Alignment, Body, CharacterState, Content, ControlAction, ControlEvent, Controller,
|
||||||
HealthChange, InputKind, InventoryAction, Pos, Scale, UnresolvedChatMsg, UtteranceKind,
|
HealthChange, InputKind, InventoryAction, Pos, Scale, UnresolvedChatMsg, UtteranceKind,
|
||||||
},
|
},
|
||||||
effect::{BuffEffect, Effect},
|
effect::{BuffEffect, Effect},
|
||||||
@ -1529,10 +1529,10 @@ impl<'a> AgentData<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn chat_npc(&self, msg: impl ToString, event_emitter: &mut Emitter<'_, ServerEvent>) {
|
pub fn chat_npc(&self, key: impl ToString, event_emitter: &mut Emitter<'_, ServerEvent>) {
|
||||||
event_emitter.emit(ServerEvent::Chat(UnresolvedChatMsg::npc(
|
event_emitter.emit(ServerEvent::Chat(UnresolvedChatMsg::npc(
|
||||||
*self.uid,
|
*self.uid,
|
||||||
msg.to_string(),
|
Content::localized(key),
|
||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2745,7 +2745,7 @@ fn handle_tell(
|
|||||||
} else {
|
} else {
|
||||||
message_opt.join(" ")
|
message_opt.join(" ")
|
||||||
};
|
};
|
||||||
server.state.send_chat(mode.new_message(target_uid, msg));
|
server.state.send_chat(mode.to_plain_msg(target_uid, msg));
|
||||||
server.notify_client(target, ServerGeneral::ChatMode(mode));
|
server.notify_client(target, ServerGeneral::ChatMode(mode));
|
||||||
Ok(())
|
Ok(())
|
||||||
} else {
|
} else {
|
||||||
@ -2770,7 +2770,7 @@ fn handle_faction(
|
|||||||
let msg = args.join(" ");
|
let msg = args.join(" ");
|
||||||
if !msg.is_empty() {
|
if !msg.is_empty() {
|
||||||
if let Some(uid) = server.state.ecs().read_storage().get(target) {
|
if let Some(uid) = server.state.ecs().read_storage().get(target) {
|
||||||
server.state.send_chat(mode.new_message(*uid, msg));
|
server.state.send_chat(mode.to_plain_msg(*uid, msg));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
server.notify_client(target, ServerGeneral::ChatMode(mode));
|
server.notify_client(target, ServerGeneral::ChatMode(mode));
|
||||||
@ -2797,7 +2797,7 @@ fn handle_group(
|
|||||||
let msg = args.join(" ");
|
let msg = args.join(" ");
|
||||||
if !msg.is_empty() {
|
if !msg.is_empty() {
|
||||||
if let Some(uid) = server.state.ecs().read_storage().get(target) {
|
if let Some(uid) = server.state.ecs().read_storage().get(target) {
|
||||||
server.state.send_chat(mode.new_message(*uid, msg));
|
server.state.send_chat(mode.to_plain_msg(*uid, msg));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
server.notify_client(target, ServerGeneral::ChatMode(mode));
|
server.notify_client(target, ServerGeneral::ChatMode(mode));
|
||||||
@ -2921,7 +2921,7 @@ fn handle_region(
|
|||||||
let msg = args.join(" ");
|
let msg = args.join(" ");
|
||||||
if !msg.is_empty() {
|
if !msg.is_empty() {
|
||||||
if let Some(uid) = server.state.ecs().read_storage().get(target) {
|
if let Some(uid) = server.state.ecs().read_storage().get(target) {
|
||||||
server.state.send_chat(mode.new_message(*uid, msg));
|
server.state.send_chat(mode.to_plain_msg(*uid, msg));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
server.notify_client(target, ServerGeneral::ChatMode(mode));
|
server.notify_client(target, ServerGeneral::ChatMode(mode));
|
||||||
@ -2942,7 +2942,7 @@ fn handle_say(
|
|||||||
let msg = args.join(" ");
|
let msg = args.join(" ");
|
||||||
if !msg.is_empty() {
|
if !msg.is_empty() {
|
||||||
if let Some(uid) = server.state.ecs().read_storage().get(target) {
|
if let Some(uid) = server.state.ecs().read_storage().get(target) {
|
||||||
server.state.send_chat(mode.new_message(*uid, msg));
|
server.state.send_chat(mode.to_plain_msg(*uid, msg));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
server.notify_client(target, ServerGeneral::ChatMode(mode));
|
server.notify_client(target, ServerGeneral::ChatMode(mode));
|
||||||
@ -2963,7 +2963,7 @@ fn handle_world(
|
|||||||
let msg = args.join(" ");
|
let msg = args.join(" ");
|
||||||
if !msg.is_empty() {
|
if !msg.is_empty() {
|
||||||
if let Some(uid) = server.state.ecs().read_storage().get(target) {
|
if let Some(uid) = server.state.ecs().read_storage().get(target) {
|
||||||
server.state.send_chat(mode.new_message(*uid, msg));
|
server.state.send_chat(mode.to_plain_msg(*uid, msg));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
server.notify_client(target, ServerGeneral::ChatMode(mode));
|
server.notify_client(target, ServerGeneral::ChatMode(mode));
|
||||||
@ -2992,8 +2992,9 @@ fn handle_join_faction(
|
|||||||
.flatten()
|
.flatten()
|
||||||
.map(|f| f.0);
|
.map(|f| f.0);
|
||||||
server.state.send_chat(
|
server.state.send_chat(
|
||||||
|
// TODO: Localise
|
||||||
ChatType::FactionMeta(faction.clone())
|
ChatType::FactionMeta(faction.clone())
|
||||||
.chat_msg(format!("[{}] joined faction ({})", alias, faction)),
|
.into_plain_msg(format!("[{}] joined faction ({})", alias, faction)),
|
||||||
);
|
);
|
||||||
(faction_join, mode)
|
(faction_join, mode)
|
||||||
} else {
|
} else {
|
||||||
@ -3009,8 +3010,9 @@ fn handle_join_faction(
|
|||||||
};
|
};
|
||||||
if let Some(faction) = faction_leave {
|
if let Some(faction) = faction_leave {
|
||||||
server.state.send_chat(
|
server.state.send_chat(
|
||||||
|
// TODO: Localise
|
||||||
ChatType::FactionMeta(faction.clone())
|
ChatType::FactionMeta(faction.clone())
|
||||||
.chat_msg(format!("[{}] left faction ({})", alias, faction)),
|
.into_plain_msg(format!("[{}] left faction ({})", alias, faction)),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
server.notify_client(target, ServerGeneral::ChatMode(mode));
|
server.notify_client(target, ServerGeneral::ChatMode(mode));
|
||||||
|
@ -35,7 +35,6 @@ use common::{
|
|||||||
};
|
};
|
||||||
use common_net::{msg::ServerGeneral, sync::WorldSyncExt};
|
use common_net::{msg::ServerGeneral, sync::WorldSyncExt};
|
||||||
use common_state::BlockChange;
|
use common_state::BlockChange;
|
||||||
use comp::chat::GenericChatMsg;
|
|
||||||
use hashbrown::HashSet;
|
use hashbrown::HashSet;
|
||||||
use rand::{distributions::WeightedIndex, Rng};
|
use rand::{distributions::WeightedIndex, Rng};
|
||||||
use rand_distr::Distribution;
|
use rand_distr::Distribution;
|
||||||
@ -198,10 +197,7 @@ pub fn handle_destroy(server: &mut Server, entity: EcsEntity, last_change: Healt
|
|||||||
_ => KillSource::Other,
|
_ => KillSource::Other,
|
||||||
};
|
};
|
||||||
|
|
||||||
state.send_chat(GenericChatMsg {
|
state.send_chat(comp::ChatType::Kill(kill_source, *uid).into_plain_msg(""));
|
||||||
chat_type: comp::ChatType::Kill(kill_source, *uid),
|
|
||||||
message: "".to_string(),
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -797,7 +797,11 @@ impl StateExt for State {
|
|||||||
(*ecs.read_resource::<UidAllocator>())
|
(*ecs.read_resource::<UidAllocator>())
|
||||||
.retrieve_entity_internal(sender.0)
|
.retrieve_entity_internal(sender.0)
|
||||||
.map_or(false, |e| {
|
.map_or(false, |e| {
|
||||||
self.validate_chat_msg(e, &msg.chat_type, &msg.message)
|
self.validate_chat_msg(
|
||||||
|
e,
|
||||||
|
&msg.chat_type,
|
||||||
|
msg.content().as_plain().unwrap_or_default(),
|
||||||
|
)
|
||||||
})
|
})
|
||||||
}) {
|
}) {
|
||||||
match &msg.chat_type {
|
match &msg.chat_type {
|
||||||
@ -827,7 +831,6 @@ impl StateExt for State {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
comp::ChatType::Kill(kill_source, uid) => {
|
comp::ChatType::Kill(kill_source, uid) => {
|
||||||
let comp::chat::GenericChatMsg { message, .. } = msg;
|
|
||||||
let clients = ecs.read_storage::<Client>();
|
let clients = ecs.read_storage::<Client>();
|
||||||
let clients_count = clients.count();
|
let clients_count = clients.count();
|
||||||
// Avoid chat spam, send kill message only to group or nearby players if a
|
// Avoid chat spam, send kill message only to group or nearby players if a
|
||||||
@ -870,7 +873,7 @@ impl StateExt for State {
|
|||||||
} else {
|
} else {
|
||||||
self.notify_players(ServerGeneral::server_msg(
|
self.notify_players(ServerGeneral::server_msg(
|
||||||
comp::ChatType::Kill(kill_source.clone(), *uid),
|
comp::ChatType::Kill(kill_source.clone(), *uid),
|
||||||
message,
|
msg.content().clone(),
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -900,7 +903,7 @@ impl StateExt for State {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
comp::ChatType::Npc(uid, _r) => {
|
comp::ChatType::Npc(uid) => {
|
||||||
let entity_opt =
|
let entity_opt =
|
||||||
(*ecs.read_resource::<UidAllocator>()).retrieve_entity_internal(uid.0);
|
(*ecs.read_resource::<UidAllocator>()).retrieve_entity_internal(uid.0);
|
||||||
|
|
||||||
@ -913,7 +916,7 @@ impl StateExt for State {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
comp::ChatType::NpcSay(uid, _r) => {
|
comp::ChatType::NpcSay(uid) => {
|
||||||
let entity_opt =
|
let entity_opt =
|
||||||
(*ecs.read_resource::<UidAllocator>()).retrieve_entity_internal(uid.0);
|
(*ecs.read_resource::<UidAllocator>()).retrieve_entity_internal(uid.0);
|
||||||
|
|
||||||
@ -926,7 +929,7 @@ impl StateExt for State {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
comp::ChatType::NpcTell(from, to, _r) => {
|
comp::ChatType::NpcTell(from, to) => {
|
||||||
for (client, uid) in
|
for (client, uid) in
|
||||||
(&ecs.read_storage::<Client>(), &ecs.read_storage::<Uid>()).join()
|
(&ecs.read_storage::<Client>(), &ecs.read_storage::<Uid>()).join()
|
||||||
{
|
{
|
||||||
@ -950,12 +953,10 @@ impl StateExt for State {
|
|||||||
comp::ChatType::Group(from, g) => {
|
comp::ChatType::Group(from, g) => {
|
||||||
if group_info.is_none() {
|
if group_info.is_none() {
|
||||||
// group not found, reply with command error
|
// group not found, reply with command error
|
||||||
let reply = comp::ChatMsg {
|
let reply = comp::ChatType::CommandError.into_plain_msg(
|
||||||
chat_type: comp::ChatType::CommandError,
|
"You are using group chat but do not belong to a group. Use /world or \
|
||||||
message: "You are using group chat but do not belong to a group. Use \
|
/region to change chat.",
|
||||||
/world or /region to change chat."
|
);
|
||||||
.into(),
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Some((client, _)) =
|
if let Some((client, _)) =
|
||||||
(&ecs.read_storage::<Client>(), &ecs.read_storage::<Uid>())
|
(&ecs.read_storage::<Client>(), &ecs.read_storage::<Uid>())
|
||||||
|
@ -6,7 +6,8 @@ use common::{
|
|||||||
inventory::item::{ItemTag, MaterialStatManifest},
|
inventory::item::{ItemTag, MaterialStatManifest},
|
||||||
invite::{InviteKind, InviteResponse},
|
invite::{InviteKind, InviteResponse},
|
||||||
tool::AbilityMap,
|
tool::AbilityMap,
|
||||||
BehaviorState, ControlAction, Item, TradingBehavior, UnresolvedChatMsg, UtteranceKind,
|
BehaviorState, Content, ControlAction, Item, TradingBehavior, UnresolvedChatMsg,
|
||||||
|
UtteranceKind,
|
||||||
},
|
},
|
||||||
event::ServerEvent,
|
event::ServerEvent,
|
||||||
rtsim::PersonalityTrait,
|
rtsim::PersonalityTrait,
|
||||||
@ -434,7 +435,7 @@ pub fn handle_inbox_update_pending_trade(bdata: &mut BehaviorData) -> bool {
|
|||||||
let (tradeid, pending, prices, inventories) = *boxval;
|
let (tradeid, pending, prices, inventories) = *boxval;
|
||||||
if agent.behavior.is(BehaviorState::TRADING) {
|
if agent.behavior.is(BehaviorState::TRADING) {
|
||||||
let who = usize::from(!agent.behavior.is(BehaviorState::TRADING_ISSUER));
|
let who = usize::from(!agent.behavior.is(BehaviorState::TRADING_ISSUER));
|
||||||
let mut message = |msg| {
|
let mut message = |text| {
|
||||||
if let Some(with) = agent
|
if let Some(with) = agent
|
||||||
.target
|
.target
|
||||||
.as_ref()
|
.as_ref()
|
||||||
@ -443,12 +444,14 @@ pub fn handle_inbox_update_pending_trade(bdata: &mut BehaviorData) -> bool {
|
|||||||
event_emitter.emit(ServerEvent::Chat(UnresolvedChatMsg::npc_tell(
|
event_emitter.emit(ServerEvent::Chat(UnresolvedChatMsg::npc_tell(
|
||||||
*agent_data.uid,
|
*agent_data.uid,
|
||||||
*with,
|
*with,
|
||||||
msg,
|
// TODO: localise this
|
||||||
|
Content::Plain(text),
|
||||||
)));
|
)));
|
||||||
} else {
|
} else {
|
||||||
event_emitter.emit(ServerEvent::Chat(UnresolvedChatMsg::npc_say(
|
event_emitter.emit(ServerEvent::Chat(UnresolvedChatMsg::npc_say(
|
||||||
*agent_data.uid,
|
*agent_data.uid,
|
||||||
msg,
|
// TODO: localise this
|
||||||
|
Content::Plain(text),
|
||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -11,7 +11,7 @@ use crate::{
|
|||||||
EditableSettings,
|
EditableSettings,
|
||||||
};
|
};
|
||||||
use common::{
|
use common::{
|
||||||
comp::{Admin, AdminRole, ChatType, Player, Presence, UnresolvedChatMsg, Waypoint},
|
comp::{Admin, AdminRole, ChatType, Player, Presence, Waypoint},
|
||||||
event::{EventBus, ServerEvent},
|
event::{EventBus, ServerEvent},
|
||||||
resources::Time,
|
resources::Time,
|
||||||
terrain::TerrainChunkSize,
|
terrain::TerrainChunkSize,
|
||||||
@ -48,7 +48,7 @@ impl Sys {
|
|||||||
if !editable_settings.server_description.is_empty() {
|
if !editable_settings.server_description.is_empty() {
|
||||||
client.send(ServerGeneral::server_msg(
|
client.send(ServerGeneral::server_msg(
|
||||||
ChatType::CommandInfo,
|
ChatType::CommandInfo,
|
||||||
&*editable_settings.server_description,
|
editable_settings.server_description.as_str(),
|
||||||
))?;
|
))?;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -62,10 +62,9 @@ impl Sys {
|
|||||||
|
|
||||||
if !client.login_msg_sent.load(Ordering::Relaxed) {
|
if !client.login_msg_sent.load(Ordering::Relaxed) {
|
||||||
if let Some(player_uid) = uids.get(entity) {
|
if let Some(player_uid) = uids.get(entity) {
|
||||||
server_emitter.emit(ServerEvent::Chat(UnresolvedChatMsg {
|
server_emitter.emit(ServerEvent::Chat(
|
||||||
chat_type: ChatType::Online(*player_uid),
|
ChatType::Online(*player_uid).into_plain_msg(""),
|
||||||
message: "".to_string(),
|
));
|
||||||
}));
|
|
||||||
|
|
||||||
client.login_msg_sent.store(true, Ordering::Relaxed);
|
client.login_msg_sent.store(true, Ordering::Relaxed);
|
||||||
}
|
}
|
||||||
|
@ -28,7 +28,7 @@ impl Sys {
|
|||||||
const CHAT_MODE_DEFAULT: &ChatMode = &ChatMode::default();
|
const CHAT_MODE_DEFAULT: &ChatMode = &ChatMode::default();
|
||||||
let mode = chat_modes.get(entity).unwrap_or(CHAT_MODE_DEFAULT);
|
let mode = chat_modes.get(entity).unwrap_or(CHAT_MODE_DEFAULT);
|
||||||
// Send chat message
|
// Send chat message
|
||||||
server_emitter.emit(ServerEvent::Chat(mode.new_message(*from, message)));
|
server_emitter.emit(ServerEvent::Chat(mode.to_plain_msg(*from, message)));
|
||||||
} else {
|
} else {
|
||||||
error!("Could not send message. Missing player uid");
|
error!("Could not send message. Missing player uid");
|
||||||
}
|
}
|
||||||
|
@ -11,3 +11,4 @@ common = {package = "veloren-common", path = "../../common"}
|
|||||||
i18n = {package = "veloren-client-i18n", path = "../../client/i18n"}
|
i18n = {package = "veloren-client-i18n", path = "../../client/i18n"}
|
||||||
# Utility
|
# Utility
|
||||||
tracing = "0.1"
|
tracing = "0.1"
|
||||||
|
hashbrown = { version = "0.12" }
|
||||||
|
@ -1,19 +1,32 @@
|
|||||||
#![feature(let_chains)]
|
#![feature(let_chains)]
|
||||||
use common::comp::{
|
use common::comp::{
|
||||||
chat::{KillSource, KillType},
|
chat::{KillSource, KillType},
|
||||||
BuffKind, ChatMsg, ChatType,
|
BuffKind, ChatMsg, ChatType, Content,
|
||||||
};
|
};
|
||||||
use common_net::msg::{ChatTypeContext, PlayerInfo};
|
use common_net::msg::{ChatTypeContext, PlayerInfo};
|
||||||
|
use hashbrown::HashMap;
|
||||||
use i18n::Localization;
|
use i18n::Localization;
|
||||||
|
|
||||||
|
pub fn make_localizer(
|
||||||
|
localisation: &Localization,
|
||||||
|
) -> impl Fn(&str, u16, &HashMap<String, String>) -> String + Copy + '_ {
|
||||||
|
move |key: &str, seed: u16, args: &HashMap<String, String>| {
|
||||||
|
localisation
|
||||||
|
.get_variation_ctx(key, seed, &args.iter().collect())
|
||||||
|
.into_owned()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn localize_chat_message(
|
pub fn localize_chat_message(
|
||||||
mut msg: ChatMsg,
|
msg: ChatMsg,
|
||||||
lookup_fn: impl Fn(&ChatMsg) -> ChatTypeContext,
|
lookup_fn: impl Fn(&ChatMsg) -> ChatTypeContext,
|
||||||
localisation: &Localization,
|
localisation: &Localization,
|
||||||
show_char_name: bool,
|
show_char_name: bool,
|
||||||
) -> ChatMsg {
|
) -> (ChatType<String>, String) {
|
||||||
let info = lookup_fn(&msg);
|
let info = lookup_fn(&msg);
|
||||||
|
|
||||||
|
let localizer = make_localizer(localisation);
|
||||||
|
|
||||||
let name_format = |uid: &common::uid::Uid| match info.player_alias.get(uid).cloned() {
|
let name_format = |uid: &common::uid::Uid| match info.player_alias.get(uid).cloned() {
|
||||||
Some(pi) => insert_alias(info.you == *uid, pi, localisation),
|
Some(pi) => insert_alias(info.you == *uid, pi, localisation),
|
||||||
None => info
|
None => info
|
||||||
@ -23,13 +36,14 @@ pub fn localize_chat_message(
|
|||||||
.expect("client didn't proved enough info"),
|
.expect("client didn't proved enough info"),
|
||||||
};
|
};
|
||||||
|
|
||||||
let message_format = |from: &common::uid::Uid, message: &str, group: Option<&String>| {
|
let message_format = |from: &common::uid::Uid, content: &Content, group: Option<&String>| {
|
||||||
let alias = name_format(from);
|
let alias = name_format(from);
|
||||||
let name = if let Some(pi) = info.player_alias.get(from).cloned() && show_char_name {
|
let name = if let Some(pi) = info.player_alias.get(from).cloned() && show_char_name {
|
||||||
pi.character.map(|c| c.name)
|
pi.character.map(|c| c.name)
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
let message = content.localize(localizer);
|
||||||
match (group, name) {
|
match (group, name) {
|
||||||
(Some(group), None) => format!("({group}) [{alias}]: {message}"),
|
(Some(group), None) => format!("({group}) [{alias}]: {message}"),
|
||||||
(None, None) => format!("[{alias}]: {message}"),
|
(None, None) => format!("[{alias}]: {message}"),
|
||||||
@ -50,40 +64,40 @@ pub fn localize_chat_message(
|
|||||||
),
|
),
|
||||||
})
|
})
|
||||||
.into_owned(),
|
.into_owned(),
|
||||||
ChatType::CommandError => msg.message.to_string(),
|
ChatType::CommandError => msg.content().localize(localizer),
|
||||||
ChatType::CommandInfo => msg.message.to_string(),
|
ChatType::CommandInfo => msg.content().localize(localizer),
|
||||||
ChatType::FactionMeta(_) => msg.message.to_string(),
|
ChatType::FactionMeta(_) => msg.content().localize(localizer),
|
||||||
ChatType::GroupMeta(_) => msg.message.to_string(),
|
ChatType::GroupMeta(_) => msg.content().localize(localizer),
|
||||||
ChatType::Tell(from, to) => {
|
ChatType::Tell(from, to) => {
|
||||||
let from_alias = name_format(from);
|
let from_alias = name_format(from);
|
||||||
let to_alias = name_format(to);
|
let to_alias = name_format(to);
|
||||||
// TODO: internationalise
|
// TODO: internationalise
|
||||||
if *from == info.you {
|
if *from == info.you {
|
||||||
format!("To [{to_alias}]: {}", msg.message)
|
format!("To [{to_alias}]: {}", msg.content().localize(localizer))
|
||||||
} else {
|
} else {
|
||||||
format!("From [{from_alias}]: {}", msg.message)
|
format!("From [{from_alias}]: {}", msg.content().localize(localizer))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
ChatType::Say(uid) => message_format(uid, &msg.message, None),
|
ChatType::Say(uid) => message_format(uid, msg.content(), None),
|
||||||
ChatType::Group(uid, s) => message_format(uid, &msg.message, Some(s)),
|
ChatType::Group(uid, s) => message_format(uid, msg.content(), Some(s)),
|
||||||
ChatType::Faction(uid, s) => message_format(uid, &msg.message, Some(s)),
|
ChatType::Faction(uid, s) => message_format(uid, msg.content(), Some(s)),
|
||||||
ChatType::Region(uid) => message_format(uid, &msg.message, None),
|
ChatType::Region(uid) => message_format(uid, msg.content(), None),
|
||||||
ChatType::World(uid) => message_format(uid, &msg.message, None),
|
ChatType::World(uid) => message_format(uid, msg.content(), None),
|
||||||
// NPCs can't talk. Should be filtered by hud/mod.rs for voxygen and
|
// NPCs can't talk. Should be filtered by hud/mod.rs for voxygen and
|
||||||
// should be filtered by server (due to not having a Pos) for chat-cli
|
// should be filtered by server (due to not having a Pos) for chat-cli
|
||||||
ChatType::Npc(uid, _r) => message_format(uid, &msg.message, None),
|
ChatType::Npc(uid) => message_format(uid, msg.content(), None),
|
||||||
ChatType::NpcSay(uid, _r) => message_format(uid, &msg.message, None),
|
ChatType::NpcSay(uid) => message_format(uid, msg.content(), None),
|
||||||
ChatType::NpcTell(from, to, _r) => {
|
ChatType::NpcTell(from, to) => {
|
||||||
let from_alias = name_format(from);
|
let from_alias = name_format(from);
|
||||||
let to_alias = name_format(to);
|
let to_alias = name_format(to);
|
||||||
// TODO: internationalise
|
// TODO: internationalise
|
||||||
if *from == info.you {
|
if *from == info.you {
|
||||||
format!("To [{to_alias}]: {}", msg.message)
|
format!("To [{to_alias}]: {}", msg.content().localize(localizer))
|
||||||
} else {
|
} else {
|
||||||
format!("From [{from_alias}]: {}", msg.message)
|
format!("From [{from_alias}]: {}", msg.content().localize(localizer))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
ChatType::Meta => msg.message.to_string(),
|
ChatType::Meta => msg.content().localize(localizer),
|
||||||
ChatType::Kill(kill_source, victim) => {
|
ChatType::Kill(kill_source, victim) => {
|
||||||
let i18n_buff = |buff| match buff {
|
let i18n_buff = |buff| match buff {
|
||||||
BuffKind::Burning => "hud-outcome-burning",
|
BuffKind::Burning => "hud-outcome-burning",
|
||||||
@ -209,8 +223,7 @@ pub fn localize_chat_message(
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
msg.message = new_msg;
|
(msg.chat_type, new_msg)
|
||||||
msg
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn insert_alias(you: bool, info: PlayerInfo, localisation: &Localization) -> String {
|
fn insert_alias(you: bool, info: PlayerInfo, localisation: &Localization) -> String {
|
||||||
|
@ -226,8 +226,8 @@ impl<'a> Widget for Chat<'a> {
|
|||||||
for message in self.new_messages.iter() {
|
for message in self.new_messages.iter() {
|
||||||
// Log the output of commands since the ingame terminal doesn't support copying
|
// Log the output of commands since the ingame terminal doesn't support copying
|
||||||
// the output to the clipboard
|
// the output to the clipboard
|
||||||
if let ChatType::CommandInfo = message.chat_type {
|
if let ChatType::CommandInfo = &message.chat_type {
|
||||||
tracing::info!("Chat command info: {}", message.message);
|
tracing::info!("Chat command info: {:?}", message.content());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
//new messages - update chat w/ them & scroll down if at bottom of chat
|
//new messages - update chat w/ them & scroll down if at bottom of chat
|
||||||
@ -426,14 +426,6 @@ impl<'a> Widget for Chat<'a> {
|
|||||||
let messages = &state
|
let messages = &state
|
||||||
.messages
|
.messages
|
||||||
.iter()
|
.iter()
|
||||||
.map(|m| {
|
|
||||||
localize_chat_message(
|
|
||||||
m.clone(),
|
|
||||||
|msg| self.client.lookup_msg_context(msg),
|
|
||||||
self.localized_strings,
|
|
||||||
show_char_name,
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.filter(|m| {
|
.filter(|m| {
|
||||||
if let Some(chat_tab) = current_chat_tab {
|
if let Some(chat_tab) = current_chat_tab {
|
||||||
chat_tab.filter.satisfies(m, &group_members)
|
chat_tab.filter.satisfies(m, &group_members)
|
||||||
@ -447,13 +439,19 @@ impl<'a> Widget for Chat<'a> {
|
|||||||
.uid()
|
.uid()
|
||||||
.and_then(|uid| {
|
.and_then(|uid| {
|
||||||
self.client
|
self.client
|
||||||
.lookup_msg_context(&m)
|
.lookup_msg_context(m)
|
||||||
.player_alias
|
.player_alias
|
||||||
.get(&uid)
|
.get(&uid)
|
||||||
.map(|i| i.is_moderator)
|
.map(|i| i.is_moderator)
|
||||||
})
|
})
|
||||||
.unwrap_or(false);
|
.unwrap_or(false);
|
||||||
(is_moderator, m)
|
let (chat_type, text) = localize_chat_message(
|
||||||
|
m.clone(),
|
||||||
|
|msg| self.client.lookup_msg_context(msg),
|
||||||
|
self.localized_strings,
|
||||||
|
show_char_name,
|
||||||
|
);
|
||||||
|
(is_moderator, chat_type, text)
|
||||||
})
|
})
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
let n_badges = messages.iter().filter(|t| t.0).count();
|
let n_badges = messages.iter().filter(|t| t.0).count();
|
||||||
@ -478,14 +476,14 @@ impl<'a> Widget for Chat<'a> {
|
|||||||
while let Some(item) = items.next(ui) {
|
while let Some(item) = items.next(ui) {
|
||||||
// This would be easier if conrod used the v-metrics from rusttype.
|
// This would be easier if conrod used the v-metrics from rusttype.
|
||||||
if item.i < messages.len() {
|
if item.i < messages.len() {
|
||||||
let (is_moderator, message) = &messages[item.i];
|
let (is_moderator, chat_type, text) = &messages[item.i];
|
||||||
let (color, icon) = render_chat_line(&message.chat_type, self.imgs);
|
let (color, icon) = render_chat_line(chat_type, self.imgs);
|
||||||
// For each ChatType needing localization get/set matching pre-formatted
|
// For each ChatType needing localization get/set matching pre-formatted
|
||||||
// localized string. This string will be formatted with the data
|
// localized string. This string will be formatted with the data
|
||||||
// provided in ChatType in the client/src/mod.rs
|
// provided in ChatType in the client/src/mod.rs
|
||||||
// fn format_message called below
|
// fn format_message called below
|
||||||
|
|
||||||
let text = Text::new(&message.message)
|
let text = Text::new(text)
|
||||||
.font_size(self.fonts.opensans.scale(15))
|
.font_size(self.fonts.opensans.scale(15))
|
||||||
.font_id(self.fonts.opensans.conrod_id)
|
.font_id(self.fonts.opensans.conrod_id)
|
||||||
.w(CHAT_BOX_WIDTH - 17.0)
|
.w(CHAT_BOX_WIDTH - 17.0)
|
||||||
@ -688,10 +686,10 @@ impl<'a> Widget for Chat<'a> {
|
|||||||
if let Some(msg) = msg.strip_prefix(chat_settings.chat_cmd_prefix) {
|
if let Some(msg) = msg.strip_prefix(chat_settings.chat_cmd_prefix) {
|
||||||
match parse_cmd(msg) {
|
match parse_cmd(msg) {
|
||||||
Ok((name, args)) => events.push(Event::SendCommand(name, args)),
|
Ok((name, args)) => events.push(Event::SendCommand(name, args)),
|
||||||
Err(err) => self.new_messages.push_back(ChatMsg {
|
// TODO: Localise
|
||||||
chat_type: ChatType::CommandError,
|
Err(err) => self
|
||||||
message: err,
|
.new_messages
|
||||||
}),
|
.push_back(ChatType::CommandError.into_plain_msg(err)),
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
events.push(Event::SendMessage(msg));
|
events.push(Event::SendMessage(msg));
|
||||||
@ -783,9 +781,9 @@ fn render_chat_line(chat_type: &ChatType<String>, imgs: &Imgs) -> (Color, conrod
|
|||||||
ChatType::Faction(_uid, _s) => (FACTION_COLOR, imgs.chat_faction_small),
|
ChatType::Faction(_uid, _s) => (FACTION_COLOR, imgs.chat_faction_small),
|
||||||
ChatType::Region(_uid) => (REGION_COLOR, imgs.chat_region_small),
|
ChatType::Region(_uid) => (REGION_COLOR, imgs.chat_region_small),
|
||||||
ChatType::World(_uid) => (WORLD_COLOR, imgs.chat_world_small),
|
ChatType::World(_uid) => (WORLD_COLOR, imgs.chat_world_small),
|
||||||
ChatType::Npc(_uid, _r) => panic!("NPCs can't talk!"), // Should be filtered by hud/mod.rs
|
ChatType::Npc(_uid) => panic!("NPCs can't talk!"), // Should be filtered by hud/mod.rs
|
||||||
ChatType::NpcSay(_uid, _r) => (SAY_COLOR, imgs.chat_say_small),
|
ChatType::NpcSay(_uid) => (SAY_COLOR, imgs.chat_say_small),
|
||||||
ChatType::NpcTell(_from, _to, _r) => (TELL_COLOR, imgs.chat_tell_small),
|
ChatType::NpcTell(_from, _to) => (TELL_COLOR, imgs.chat_tell_small),
|
||||||
ChatType::Meta => (INFO_COLOR, imgs.chat_command_info_small),
|
ChatType::Meta => (INFO_COLOR, imgs.chat_command_info_small),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3278,7 +3278,7 @@ impl Hud {
|
|||||||
|
|
||||||
// Don't put NPC messages in chat box.
|
// Don't put NPC messages in chat box.
|
||||||
self.new_messages
|
self.new_messages
|
||||||
.retain(|m| !matches!(m.chat_type, comp::ChatType::Npc(_, _)));
|
.retain(|m| !matches!(m.chat_type, comp::ChatType::Npc(_)));
|
||||||
|
|
||||||
// Chat box
|
// Chat box
|
||||||
if global_state.settings.interface.toggle_chat {
|
if global_state.settings.interface.toggle_chat {
|
||||||
|
@ -20,6 +20,7 @@ use conrod_core::{
|
|||||||
widget_ids, Color, Colorable, Positionable, Sizeable, Widget, WidgetCommon,
|
widget_ids, Color, Colorable, Positionable, Sizeable, Widget, WidgetCommon,
|
||||||
};
|
};
|
||||||
use i18n::Localization;
|
use i18n::Localization;
|
||||||
|
use i18n_helpers::make_localizer;
|
||||||
use keyboard_keynames::key_layout::KeyLayout;
|
use keyboard_keynames::key_layout::KeyLayout;
|
||||||
|
|
||||||
const MAX_BUBBLE_WIDTH: f64 = 250.0;
|
const MAX_BUBBLE_WIDTH: f64 = 250.0;
|
||||||
@ -534,8 +535,7 @@ impl<'a> Widget for Overhead<'a> {
|
|||||||
// Speech bubble
|
// Speech bubble
|
||||||
if let Some(bubble) = self.bubble {
|
if let Some(bubble) = self.bubble {
|
||||||
let dark_mode = self.settings.speech_bubble_dark_mode;
|
let dark_mode = self.settings.speech_bubble_dark_mode;
|
||||||
let localizer = |s: &str, i| -> String { self.i18n.get_variation(s, i).to_string() };
|
let bubble_contents: String = bubble.content().localize(make_localizer(self.i18n));
|
||||||
let bubble_contents: String = bubble.message(localizer);
|
|
||||||
let (text_color, shadow_color) = bubble_color(bubble, dark_mode);
|
let (text_color, shadow_color) = bubble_color(bubble, dark_mode);
|
||||||
let mut text = Text::new(&bubble_contents)
|
let mut text = Text::new(&bubble_contents)
|
||||||
.color(text_color)
|
.color(text_color)
|
||||||
|
@ -18,7 +18,7 @@ use common::{
|
|||||||
inventory::slot::{EquipSlot, Slot},
|
inventory::slot::{EquipSlot, Slot},
|
||||||
invite::InviteKind,
|
invite::InviteKind,
|
||||||
item::{tool::ToolKind, ItemDesc},
|
item::{tool::ToolKind, ItemDesc},
|
||||||
ChatMsg, ChatType, InputKind, InventoryUpdateEvent, Pos, PresenceKind, Stats,
|
ChatType, Content, InputKind, InventoryUpdateEvent, Pos, PresenceKind, Stats,
|
||||||
UtteranceKind, Vel,
|
UtteranceKind, Vel,
|
||||||
},
|
},
|
||||||
consts::MAX_MOUNT_RANGE,
|
consts::MAX_MOUNT_RANGE,
|
||||||
@ -309,17 +309,17 @@ impl SessionState {
|
|||||||
InviteAnswer::TimedOut => "timed out",
|
InviteAnswer::TimedOut => "timed out",
|
||||||
};
|
};
|
||||||
let msg = format!("{} invite to {} {}", kind_str, target_name, answer_str);
|
let msg = format!("{} invite to {} {}", kind_str, target_name, answer_str);
|
||||||
self.hud.new_message(ChatType::Meta.chat_msg(msg));
|
// TODO: Localise
|
||||||
|
self.hud.new_message(ChatType::Meta.into_plain_msg(msg));
|
||||||
},
|
},
|
||||||
client::Event::TradeComplete { result, trade: _ } => {
|
client::Event::TradeComplete { result, trade: _ } => {
|
||||||
self.hud.clear_cursor();
|
self.hud.clear_cursor();
|
||||||
let i18n = global_state.i18n.read();
|
self.hud
|
||||||
let msg = match result {
|
.new_message(ChatType::Meta.into_msg(Content::localized(match result {
|
||||||
TradeResult::Completed => i18n.get_msg("hud-trade-result-completed"),
|
TradeResult::Completed => "hud-trade-result-completed",
|
||||||
TradeResult::Declined => i18n.get_msg("hud-trade-result-declined"),
|
TradeResult::Declined => "hud-trade-result-declined",
|
||||||
TradeResult::NotEnoughSpace => i18n.get_msg("hud-trade-result-nospace"),
|
TradeResult::NotEnoughSpace => "hud-trade-result-nospace",
|
||||||
};
|
})));
|
||||||
self.hud.new_message(ChatType::Meta.chat_msg(msg));
|
|
||||||
},
|
},
|
||||||
client::Event::InventoryUpdated(inv_event) => {
|
client::Event::InventoryUpdated(inv_event) => {
|
||||||
let sfx_triggers = self.scene.sfx_mgr.triggers.read();
|
let sfx_triggers = self.scene.sfx_mgr.triggers.read();
|
||||||
@ -380,21 +380,13 @@ impl SessionState {
|
|||||||
},
|
},
|
||||||
client::Event::Disconnect => return Ok(TickAction::Disconnect),
|
client::Event::Disconnect => return Ok(TickAction::Disconnect),
|
||||||
client::Event::DisconnectionNotification(time) => {
|
client::Event::DisconnectionNotification(time) => {
|
||||||
let i18n = global_state.i18n.read();
|
self.hud
|
||||||
|
.new_message(ChatType::CommandError.into_msg(match time {
|
||||||
let message = match time {
|
0 => Content::localized("hud-chat-goodbye"),
|
||||||
0 => String::from(i18n.get_msg("hud-chat-goodbye")),
|
_ => Content::localized_with_args("hud-chat-connection_lost", [(
|
||||||
_ => i18n
|
"time", time,
|
||||||
.get_msg_ctx("hud-chat-connection_lost", &i18n::fluent_args! {
|
)]),
|
||||||
"time" => time
|
}));
|
||||||
})
|
|
||||||
.into_owned(),
|
|
||||||
};
|
|
||||||
|
|
||||||
self.hud.new_message(ChatMsg {
|
|
||||||
chat_type: ChatType::CommandError,
|
|
||||||
message,
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
client::Event::Kicked(reason) => {
|
client::Event::Kicked(reason) => {
|
||||||
global_state.info_message = Some(format!(
|
global_state.info_message = Some(format!(
|
||||||
@ -992,18 +984,12 @@ impl PlayState for SessionState {
|
|||||||
|e| e.name.to_owned(),
|
|e| e.name.to_owned(),
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
let msg = global_state
|
self.hud.new_message(ChatType::Meta.into_msg(
|
||||||
.i18n
|
Content::localized_with_args(
|
||||||
.read()
|
|
||||||
.get_msg_ctx(
|
|
||||||
"hud-trade-invite_sent",
|
"hud-trade-invite_sent",
|
||||||
&i18n::fluent_args! {
|
[("playername", &name)],
|
||||||
"playername" => &name
|
),
|
||||||
},
|
));
|
||||||
)
|
|
||||||
.into_owned();
|
|
||||||
self.hud
|
|
||||||
.new_message(ChatType::Meta.chat_msg(msg));
|
|
||||||
client.send_invite(uid, InviteKind::Trade)
|
client.send_invite(uid, InviteKind::Trade)
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
@ -1115,10 +1101,11 @@ impl PlayState for SessionState {
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Event::ScreenshotMessage(screenshot_message) => self.hud.new_message(ChatMsg {
|
|
||||||
chat_type: ChatType::CommandInfo,
|
// TODO: Localise
|
||||||
message: screenshot_message,
|
Event::ScreenshotMessage(screenshot_msg) => self
|
||||||
}),
|
.hud
|
||||||
|
.new_message(ChatType::CommandInfo.into_plain_msg(screenshot_msg)),
|
||||||
|
|
||||||
Event::Zoom(delta) if self.zoom_lock => {
|
Event::Zoom(delta) if self.zoom_lock => {
|
||||||
// only fire this Hud event when player has "intent" to zoom
|
// only fire this Hud event when player has "intent" to zoom
|
||||||
@ -1414,11 +1401,15 @@ impl PlayState for SessionState {
|
|||||||
match run_command(&mut self.client.borrow_mut(), global_state, &name, args)
|
match run_command(&mut self.client.borrow_mut(), global_state, &name, args)
|
||||||
{
|
{
|
||||||
Ok(Some(info)) => {
|
Ok(Some(info)) => {
|
||||||
self.hud.new_message(ChatType::CommandInfo.chat_msg(&info))
|
// TODO: Localise
|
||||||
|
self.hud
|
||||||
|
.new_message(ChatType::CommandInfo.into_plain_msg(&info))
|
||||||
},
|
},
|
||||||
Ok(None) => {}, // Server will provide an info message
|
Ok(None) => {}, // Server will provide an info message
|
||||||
Err(error) => {
|
Err(error) => {
|
||||||
self.hud.new_message(ChatType::CommandError.chat_msg(error))
|
// TODO: Localise
|
||||||
|
self.hud
|
||||||
|
.new_message(ChatType::CommandError.into_plain_msg(error))
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
Loading…
Reference in New Issue
Block a user