diff --git a/CHANGELOG.md b/CHANGELOG.md index 08082b13e6..34948ffa28 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -102,6 +102,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Cultist Husk no longer drops weapons and armor - Animal Trainers now spawn in tier-5 dungeon and not in tier-3 - Reworked clay golem to have unique attacks. +- Merchants now use `/tell` instead of `/say` to communicate prices ### Removed diff --git a/client/src/lib.rs b/client/src/lib.rs index 0d04801bb5..df73a2a780 100644 --- a/client/src/lib.rs +++ b/client/src/lib.rs @@ -2216,20 +2216,6 @@ impl Client { let comp::ChatMsg { chat_type, message, .. } = &msg; - let alias_of_uid = |uid| { - self.player_list - .get(uid) - .map_or("".to_string(), |player_info| { - if player_info.is_moderator { - format!( - "MOD - {}", - self.personalize_alias(*uid, player_info.player_alias.clone()) - ) - } else { - self.personalize_alias(*uid, player_info.player_alias.clone()) - } - }) - }; let name_of_uid = |uid| { let ecs = self.state.ecs(); ( @@ -2240,6 +2226,21 @@ impl Client { .find(|(_, u)| u == &uid) .map(|(c, _)| c.name.clone()) }; + let alias_of_uid = |uid| { + self.player_list.get(uid).map_or( + name_of_uid(uid).unwrap_or_else(|| "".to_string()), + |player_info| { + if player_info.is_moderator { + format!( + "MOD - {}", + self.personalize_alias(*uid, player_info.player_alias.clone()) + ) + } else { + self.personalize_alias(*uid, player_info.player_alias.clone()) + } + }, + ) + }; let message_format = |uid, message, group| { let alias = alias_of_uid(uid); let name = if character_name { @@ -2401,6 +2402,15 @@ impl Client { // by server (due to not having a Pos) for chat-cli comp::ChatType::Npc(_uid, _r) => "".to_string(), comp::ChatType::NpcSay(uid, _r) => message_format(uid, message, None), + comp::ChatType::NpcTell(from, to, _r) => { + let from_alias = alias_of_uid(from); + let to_alias = alias_of_uid(to); + if Some(*from) == self.uid() { + format!("To [{}]: {}", to_alias, message) + } else { + format!("From [{}]: {}", from_alias, message) + } + }, comp::ChatType::Meta => message.to_string(), } } diff --git a/common/src/comp/chat.rs b/common/src/comp/chat.rs index 6d319c818d..a2e1b080db 100644 --- a/common/src/comp/chat.rs +++ b/common/src/comp/chat.rs @@ -108,6 +108,9 @@ pub enum ChatType { Npc(Uid, u16), /// From NPCs but in the chat for clients in the near vicinity NpcSay(Uid, u16), + /// From NPCs but in the chat for a specific client. Shows a chat bubble. + /// (from, to, localization variant) + NpcTell(Uid, Uid, u16), /// Anything else Meta, // Looted items @@ -152,6 +155,11 @@ impl GenericChatMsg { Self { chat_type, message } } + pub fn npc_tell(from: Uid, to: Uid, message: String) -> Self { + let chat_type = ChatType::NpcTell(from, to, rand::random()); + Self { chat_type, message } + } + pub fn map_group(self, mut f: impl FnMut(G) -> T) -> GenericChatMsg { let chat_type = match self.chat_type { ChatType::Online(a) => ChatType::Online(a), @@ -170,6 +178,7 @@ impl GenericChatMsg { ChatType::World(a) => ChatType::World(a), ChatType::Npc(a, b) => ChatType::Npc(a, b), ChatType::NpcSay(a, b) => ChatType::NpcSay(a, b), + ChatType::NpcTell(a, b, c) => ChatType::NpcTell(a, b, c), ChatType::Meta => ChatType::Meta, }; @@ -189,7 +198,9 @@ impl GenericChatMsg { pub fn to_bubble(&self) -> Option<(SpeechBubble, Uid)> { let icon = self.icon(); - if let ChatType::Npc(from, r) | ChatType::NpcSay(from, r) = self.chat_type { + if let ChatType::Npc(from, r) | ChatType::NpcSay(from, r) | ChatType::NpcTell(from, _, r) = + self.chat_type + { Some((SpeechBubble::npc_new(&self.message, r, icon), from)) } else { self.uid() @@ -215,6 +226,7 @@ impl GenericChatMsg { ChatType::World(_u) => SpeechBubbleType::World, ChatType::Npc(_u, _r) => SpeechBubbleType::None, ChatType::NpcSay(_u, _r) => SpeechBubbleType::Say, + ChatType::NpcTell(_f, _t, _) => SpeechBubbleType::Say, ChatType::Meta => SpeechBubbleType::None, } } @@ -237,6 +249,7 @@ impl GenericChatMsg { ChatType::World(u) => Some(*u), ChatType::Npc(u, _r) => Some(*u), ChatType::NpcSay(u, _r) => Some(*u), + ChatType::NpcTell(u, _t, _r) => Some(*u), ChatType::Meta => None, } } diff --git a/server/src/state_ext.rs b/server/src/state_ext.rs index 2301a20813..0088b4ca92 100644 --- a/server/src/state_ext.rs +++ b/server/src/state_ext.rs @@ -654,6 +654,15 @@ impl StateExt for State { } } }, + comp::ChatType::NpcTell(from, to, _r) => { + for (client, uid) in + (&ecs.read_storage::(), &ecs.read_storage::()).join() + { + if uid == from || uid == to { + client.send_fallible(ServerGeneral::ChatMsg(resolved_msg.clone())); + } + } + }, comp::ChatType::FactionMeta(s) | comp::ChatType::Faction(_, s) => { for (client, faction) in ( &ecs.read_storage::(), diff --git a/server/src/sys/agent.rs b/server/src/sys/agent.rs index 9b0812edb7..c91d1a418f 100644 --- a/server/src/sys/agent.rs +++ b/server/src/sys/agent.rs @@ -1276,9 +1276,21 @@ impl<'a> AgentData<'a> { "That only covers {:.1}% of my costs!", balance0 / balance1 * 100.0 ); - event_emitter.emit(ServerEvent::Chat(UnresolvedChatMsg::npc_say( - *self.uid, msg, - ))); + if let Some(tgt_data) = &agent.target { + if let Some(with) = read_data.uids.get(tgt_data.target) { + event_emitter.emit(ServerEvent::Chat( + UnresolvedChatMsg::npc_tell(*self.uid, *with, msg), + )); + } else { + event_emitter.emit(ServerEvent::Chat( + UnresolvedChatMsg::npc_say(*self.uid, msg), + )); + } + } else { + event_emitter.emit(ServerEvent::Chat(UnresolvedChatMsg::npc_say( + *self.uid, msg, + ))); + } } if pending.phase != TradePhase::Mutate { // we got into the review phase but without balanced goods, decline @@ -1461,16 +1473,20 @@ impl<'a> AgentData<'a> { && !invulnerability_is_in_buffs(read_data.buffs.get(*e)) && (try_owner_alignment(self.alignment, &read_data).and_then(|a| try_owner_alignment(*e_alignment, &read_data).map(|b| a.hostile_towards(*b))).unwrap_or(false) || ( if let Some(rtsim_entity) = &self.rtsim_entity { - if rtsim_entity.brain.remembers_fight_with_character(&e_stats.name) { - agent.rtsim_controller.events.push( - RtSimEvent::AddMemory(Memory { - item: MemoryItem::CharacterFight { name: e_stats.name.clone() }, - time_to_forget: read_data.time.0 + 300.0, - }) - ); - let msg = format!("{}! How dare you cross me again!", e_stats.name.clone()); - event_emitter.emit(ServerEvent::Chat(UnresolvedChatMsg::npc(*self.uid, msg))); - true + if agent.behavior.can(BehaviorCapability::SPEAK) { + if rtsim_entity.brain.remembers_fight_with_character(&e_stats.name) { + agent.rtsim_controller.events.push( + RtSimEvent::AddMemory(Memory { + item: MemoryItem::CharacterFight { name: e_stats.name.clone() }, + time_to_forget: read_data.time.0 + 300.0, + }) + ); + let msg = format!("{}! How dare you cross me again!", e_stats.name.clone()); + event_emitter.emit(ServerEvent::Chat(UnresolvedChatMsg::npc(*self.uid, msg))); + true + } else { + false + } } else { false } diff --git a/voxygen/src/hud/chat.rs b/voxygen/src/hud/chat.rs index 0795e3c930..188590b656 100644 --- a/voxygen/src/hud/chat.rs +++ b/voxygen/src/hud/chat.rs @@ -619,6 +619,7 @@ fn render_chat_line(chat_type: &ChatType, imgs: &Imgs) -> (Color, conrod 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::NpcSay(_uid, _r) => (SAY_COLOR, imgs.chat_say_small), + ChatType::NpcTell(_from, _to, _r) => (TELL_COLOR, imgs.chat_tell_small), ChatType::Meta => (INFO_COLOR, imgs.chat_command_info_small), } }