From d856c202253df75c6f224b92819be70b9fedb093 Mon Sep 17 00:00:00 2001 From: Imbris Date: Sun, 12 Jul 2020 16:18:57 -0400 Subject: [PATCH] Integrate groups with chat groups --- common/src/cmd.rs | 16 ++++----- common/src/comp/chat.rs | 75 ++++++++++++++++++++++++--------------- common/src/comp/group.rs | 12 ++++--- common/src/comp/mod.rs | 3 +- common/src/event.rs | 2 +- common/src/state.rs | 1 - common/src/sys/agent.rs | 9 ++--- server/src/cmd.rs | 13 +++---- server/src/state_ext.rs | 32 ++++++++++------- server/src/sys/message.rs | 8 ++--- voxygen/src/hud/chat.rs | 2 +- 11 files changed, 101 insertions(+), 72 deletions(-) diff --git a/common/src/cmd.rs b/common/src/cmd.rs index 812af83795..04ead5547d 100644 --- a/common/src/cmd.rs +++ b/common/src/cmd.rs @@ -50,7 +50,7 @@ pub enum ChatCommand { Health, Help, JoinFaction, - JoinGroup, + //JoinGroup, Jump, Kill, KillNpcs, @@ -92,7 +92,7 @@ pub static CHAT_COMMANDS: &[ChatCommand] = &[ ChatCommand::Health, ChatCommand::Help, ChatCommand::JoinFaction, - ChatCommand::JoinGroup, + //ChatCommand::JoinGroup, ChatCommand::Jump, ChatCommand::Kill, ChatCommand::KillNpcs, @@ -246,11 +246,11 @@ impl ChatCommand { "Join/leave the specified faction", NoAdmin, ), - ChatCommand::JoinGroup => ChatCommandData::new( - vec![Any("group", Optional)], - "Join/leave the specified group", - NoAdmin, - ), + //ChatCommand::JoinGroup => ChatCommandData::new( + // vec![Any("group", Optional)], + // "Join/leave the specified group", + // NoAdmin, + //), ChatCommand::Jump => cmd( vec![ Float("x", 0.0, Required), @@ -383,7 +383,7 @@ impl ChatCommand { ChatCommand::Group => "group", ChatCommand::Health => "health", ChatCommand::JoinFaction => "join_faction", - ChatCommand::JoinGroup => "join_group", + //ChatCommand::JoinGroup => "join_group", ChatCommand::Help => "help", ChatCommand::Jump => "jump", ChatCommand::Kill => "kill", diff --git a/common/src/comp/chat.rs b/common/src/comp/chat.rs index 589d808ad8..019b1876bd 100644 --- a/common/src/comp/chat.rs +++ b/common/src/comp/chat.rs @@ -1,4 +1,4 @@ -use crate::{msg::ServerMsg, sync::Uid}; +use crate::{comp::group::Group, msg::ServerMsg, sync::Uid}; use serde::{Deserialize, Serialize}; use specs::Component; use specs_idvs::IdvStorage; @@ -15,7 +15,7 @@ pub enum ChatMode { /// Talk to players in your region of the world Region, /// Talk to your current group of players - Group(String), + Group(Group), /// Talk to your faction Faction(String), /// Talk to every player on the server @@ -28,16 +28,16 @@ impl Component for ChatMode { impl ChatMode { /// Create a message from your current chat mode and uuid. - pub fn new_message(&self, from: Uid, message: String) -> ChatMsg { + pub fn new_message(&self, from: Uid, message: String) -> UnresolvedChatMsg { let chat_type = match self { ChatMode::Tell(to) => ChatType::Tell(from, *to), ChatMode::Say => ChatType::Say(from), ChatMode::Region => ChatType::Region(from), - ChatMode::Group(name) => ChatType::Group(from, name.to_string()), - ChatMode::Faction(name) => ChatType::Faction(from, name.to_string()), + ChatMode::Group(group) => ChatType::Group(from, *group), + ChatMode::Faction(faction) => ChatType::Faction(from, faction.clone()), ChatMode::World => ChatType::World(from), }; - ChatMsg { chat_type, message } + UnresolvedChatMsg { chat_type, message } } } @@ -49,7 +49,7 @@ impl Default for ChatMode { /// /// This is a superset of `SpeechBubbleType`, which is a superset of `ChatMode` #[derive(Debug, Clone, Serialize, Deserialize)] -pub enum ChatType { +pub enum ChatType { /// A player came online Online, /// A player went offline @@ -61,7 +61,7 @@ pub enum ChatType { /// Inform players that someone died Kill, /// Server notifications to a group, such as player join/leave - GroupMeta(String), + GroupMeta(G), /// Server notifications to a faction, such as player join/leave FactionMeta(String), /// One-on-one chat (from, to) @@ -69,7 +69,7 @@ pub enum ChatType { /// Chat with nearby players Say(Uid), /// Group chat - Group(Uid, String), + Group(Uid, G), /// Factional chat Faction(Uid, String), /// Regional chat @@ -86,17 +86,18 @@ pub enum ChatType { Loot, } -impl ChatType { - pub fn chat_msg(self, msg: S) -> ChatMsg +impl ChatType { + pub fn chat_msg(self, msg: S) -> GenericChatMsg where S: Into, { - ChatMsg { + GenericChatMsg { chat_type: self, message: msg.into(), } } - +} +impl ChatType { pub fn server_msg(self, msg: S) -> ServerMsg where S: Into, @@ -106,12 +107,15 @@ impl ChatType { } #[derive(Debug, Clone, Serialize, Deserialize)] -pub struct ChatMsg { - pub chat_type: ChatType, +pub struct GenericChatMsg { + pub chat_type: ChatType, pub message: String, } -impl ChatMsg { +pub type ChatMsg = GenericChatMsg; +pub type UnresolvedChatMsg = GenericChatMsg; + +impl GenericChatMsg { pub const NPC_DISTANCE: f32 = 100.0; pub const REGION_DISTANCE: f32 = 1000.0; pub const SAY_DISTANCE: f32 = 100.0; @@ -121,6 +125,32 @@ impl ChatMsg { 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 => ChatType::Online, + ChatType::Offline => ChatType::Offline, + ChatType::CommandInfo => ChatType::CommandInfo, + ChatType::CommandError => ChatType::CommandError, + ChatType::Loot => ChatType::Loot, + ChatType::FactionMeta(a) => ChatType::FactionMeta(a), + ChatType::GroupMeta(g) => ChatType::GroupMeta(f(g)), + ChatType::Kill => ChatType::Kill, + 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), + ChatType::Npc(a, b) => ChatType::Npc(a, b), + ChatType::Meta => ChatType::Meta, + }; + + GenericChatMsg { + chat_type, + message: self.message, + } + } + pub fn to_bubble(&self) -> Option<(SpeechBubble, Uid)> { let icon = self.icon(); if let ChatType::Npc(from, r) = self.chat_type { @@ -174,19 +204,6 @@ impl ChatMsg { } } -/// Player groups are useful when forming raiding parties and coordinating -/// gameplay. -/// -/// Groups are currently just an associated String (the group's name) -#[derive(Clone, Debug)] -pub struct Group(pub String); -impl Component for Group { - type Storage = IdvStorage; -} -impl From for Group { - fn from(s: String) -> Self { Group(s) } -} - /// Player factions are used to coordinate pvp vs hostile factions or segment /// chat from the world /// diff --git a/common/src/comp/group.rs b/common/src/comp/group.rs index e843f32720..acbcf17fa1 100644 --- a/common/src/comp/group.rs +++ b/common/src/comp/group.rs @@ -27,11 +27,12 @@ impl Component for Group { type Storage = FlaggedStorage>; } -#[derive(Copy, Clone, Debug)] +#[derive(Clone, Debug)] pub struct GroupInfo { // TODO: what about enemy groups, either the leader will constantly change because they have to // be loaded or we create a dummy entity or this needs to be optional pub leader: specs::Entity, + pub name: String, } #[derive(Debug, Clone, Serialize, Deserialize)] @@ -110,12 +111,15 @@ pub fn members<'a>( // TODO: optimize add/remove for massive NPC groups impl GroupManager { - pub fn group_info(&self, group: Group) -> Option { - self.groups.get(group.0 as usize).copied() + pub fn group_info(&self, group: Group) -> Option<&GroupInfo> { + self.groups.get(group.0 as usize) } fn create_group(&mut self, leader: specs::Entity) -> Group { - Group(self.groups.insert(GroupInfo { leader }) as u32) + Group(self.groups.insert(GroupInfo { + leader, + name: "Flames".into(), + }) as u32) } fn remove_group(&mut self, group: Group) { self.groups.remove(group.0 as usize); } diff --git a/common/src/comp/mod.rs b/common/src/comp/mod.rs index 041a789b37..ccef80dc8b 100644 --- a/common/src/comp/mod.rs +++ b/common/src/comp/mod.rs @@ -29,9 +29,8 @@ pub use body::{ humanoid, object, quadruped_low, quadruped_medium, quadruped_small, AllBodies, Body, BodyData, }; pub use character_state::{Attacking, CharacterState, StateUpdate}; -// TODO: replace chat::Group functionality with group::Group pub use chat::{ - ChatMode, ChatMsg, ChatType, Faction, Group as ChatGroup, SpeechBubble, SpeechBubbleType, + ChatMode, ChatMsg, ChatType, Faction, SpeechBubble, SpeechBubbleType, UnresolvedChatMsg, }; pub use controller::{ Climb, ControlAction, ControlEvent, Controller, ControllerInputs, GroupManip, Input, diff --git a/common/src/event.rs b/common/src/event.rs index 1e2ba17543..9a07615c68 100644 --- a/common/src/event.rs +++ b/common/src/event.rs @@ -81,7 +81,7 @@ pub enum ServerEvent { ChunkRequest(EcsEntity, Vec2), ChatCmd(EcsEntity, String), /// Send a chat message to the player from an npc or other player - Chat(comp::ChatMsg), + Chat(comp::UnresolvedChatMsg), } pub struct EventBus { diff --git a/common/src/state.rs b/common/src/state.rs index 9d5a992e04..a76698c944 100644 --- a/common/src/state.rs +++ b/common/src/state.rs @@ -157,7 +157,6 @@ impl State { ecs.register::(); ecs.register::(); ecs.register::(); - ecs.register::(); ecs.register::(); // Register synced resources used by the ECS. diff --git a/common/src/sys/agent.rs b/common/src/sys/agent.rs index 4e0690ccac..5f3fa14869 100644 --- a/common/src/sys/agent.rs +++ b/common/src/sys/agent.rs @@ -4,8 +4,8 @@ use crate::{ agent::Activity, group, item::{tool::ToolKind, ItemKind}, - Agent, Alignment, Body, CharacterState, ChatMsg, ControlAction, Controller, Loadout, - MountState, Ori, PhysicsState, Pos, Scale, Stats, Vel, + Agent, Alignment, Body, CharacterState, ControlAction, Controller, Loadout, MountState, + Ori, PhysicsState, Pos, Scale, Stats, UnresolvedChatMsg, Vel, }, event::{EventBus, ServerEvent}, path::{Chaser, TraversalConfig}, @@ -430,8 +430,9 @@ impl<'a> System<'a> for Sys { if stats.get(attacker).map_or(false, |a| !a.is_dead) { if agent.can_speak { let msg = "npc.speech.villager_under_attack".to_string(); - event_bus - .emit_now(ServerEvent::Chat(ChatMsg::npc(*uid, msg))); + event_bus.emit_now(ServerEvent::Chat( + UnresolvedChatMsg::npc(*uid, msg), + )); } agent.activity = Activity::Attack { diff --git a/server/src/cmd.rs b/server/src/cmd.rs index 344f7c66b0..eb14d24e24 100644 --- a/server/src/cmd.rs +++ b/server/src/cmd.rs @@ -77,7 +77,7 @@ fn get_handler(cmd: &ChatCommand) -> CommandHandler { ChatCommand::Health => handle_health, ChatCommand::Help => handle_help, ChatCommand::JoinFaction => handle_join_faction, - ChatCommand::JoinGroup => handle_join_group, + //ChatCommand::JoinGroup => handle_join_group, ChatCommand::Jump => handle_jump, ChatCommand::Kill => handle_kill, ChatCommand::KillNpcs => handle_kill_npcs, @@ -1197,8 +1197,8 @@ fn handle_group( return; } let ecs = server.state.ecs(); - if let Some(comp::ChatGroup(group)) = ecs.read_storage().get(client) { - let mode = comp::ChatMode::Group(group.to_string()); + if let Some(group) = ecs.read_storage::().get(client) { + let mode = comp::ChatMode::Group(*group); let _ = ecs.write_storage().insert(client, mode.clone()); if !msg.is_empty() { if let Some(uid) = ecs.read_storage().get(client) { @@ -1208,7 +1208,7 @@ fn handle_group( } else { server.notify_client( client, - ChatType::CommandError.server_msg("Please join a group with /join_group"), + ChatType::CommandError.server_msg("Please create a group first"), ); } } @@ -1359,7 +1359,8 @@ fn handle_join_faction( } } -fn handle_join_group( +// TODO: it might be useful to copy the GroupMeta messages elsewhere +/*fn handle_join_group( server: &mut Server, client: EcsEntity, target: EcsEntity, @@ -1419,7 +1420,7 @@ fn handle_join_group( ChatType::CommandError.server_msg("Could not find your player alias"), ); } -} +}*/ #[cfg(not(feature = "worldgen"))] fn handle_debug_column( diff --git a/server/src/state_ext.rs b/server/src/state_ext.rs index 20a3189efc..d50b5ef892 100644 --- a/server/src/state_ext.rs +++ b/server/src/state_ext.rs @@ -46,7 +46,7 @@ pub trait StateExt { /// Performed after loading component data from the database fn update_character_data(&mut self, entity: EcsEntity, components: PersistedComponents); /// Iterates over registered clients and send each `ServerMsg` - fn send_chat(&self, msg: comp::ChatMsg); + fn send_chat(&self, msg: comp::UnresolvedChatMsg); fn notify_registered_clients(&self, msg: ServerMsg); /// Delete an entity, recording the deletion in [`DeletedEntities`] fn delete_entity_recorded( @@ -240,10 +240,18 @@ impl StateExt for State { /// Send the chat message to the proper players. Say and region are limited /// by location. Faction and group are limited by component. - fn send_chat(&self, msg: comp::ChatMsg) { + fn send_chat(&self, msg: comp::UnresolvedChatMsg) { let ecs = self.ecs(); let is_within = |target, a: &comp::Pos, b: &comp::Pos| a.0.distance_squared(b.0) < target * target; + + let group_manager = ecs.read_resource::(); + let resolved_msg = msg.clone().map_group(|group_id| { + group_manager + .group_info(group_id) + .map_or_else(|| "???".into(), |i| i.name.clone()) + }); + match &msg.chat_type { comp::ChatType::Online | comp::ChatType::Offline @@ -253,7 +261,7 @@ impl StateExt for State { | comp::ChatType::Kill | comp::ChatType::Meta | comp::ChatType::World(_) => { - self.notify_registered_clients(ServerMsg::ChatMsg(msg.clone())) + self.notify_registered_clients(ServerMsg::ChatMsg(resolved_msg.clone())) }, comp::ChatType::Tell(u, t) => { for (client, uid) in ( @@ -263,7 +271,7 @@ impl StateExt for State { .join() { if uid == u || uid == t { - client.notify(ServerMsg::ChatMsg(msg.clone())); + client.notify(ServerMsg::ChatMsg(resolved_msg.clone())); } } }, @@ -275,7 +283,7 @@ impl StateExt for State { if let Some(speaker_pos) = entity_opt.and_then(|e| positions.get(e)) { for (client, pos) in (&mut ecs.write_storage::(), &positions).join() { if is_within(comp::ChatMsg::SAY_DISTANCE, pos, speaker_pos) { - client.notify(ServerMsg::ChatMsg(msg.clone())); + client.notify(ServerMsg::ChatMsg(resolved_msg.clone())); } } } @@ -287,7 +295,7 @@ impl StateExt for State { if let Some(speaker_pos) = entity_opt.and_then(|e| positions.get(e)) { for (client, pos) in (&mut ecs.write_storage::(), &positions).join() { if is_within(comp::ChatMsg::REGION_DISTANCE, pos, speaker_pos) { - client.notify(ServerMsg::ChatMsg(msg.clone())); + client.notify(ServerMsg::ChatMsg(resolved_msg.clone())); } } } @@ -299,7 +307,7 @@ impl StateExt for State { if let Some(speaker_pos) = entity_opt.and_then(|e| positions.get(e)) { for (client, pos) in (&mut ecs.write_storage::(), &positions).join() { if is_within(comp::ChatMsg::NPC_DISTANCE, pos, speaker_pos) { - client.notify(ServerMsg::ChatMsg(msg.clone())); + client.notify(ServerMsg::ChatMsg(resolved_msg.clone())); } } } @@ -313,19 +321,19 @@ impl StateExt for State { .join() { if s == &faction.0 { - client.notify(ServerMsg::ChatMsg(msg.clone())); + client.notify(ServerMsg::ChatMsg(resolved_msg.clone())); } } }, - comp::ChatType::GroupMeta(s) | comp::ChatType::Group(_, s) => { + comp::ChatType::GroupMeta(g) | comp::ChatType::Group(_, g) => { for (client, group) in ( &mut ecs.write_storage::(), - &ecs.read_storage::(), + &ecs.read_storage::(), ) .join() { - if s == &group.0 { - client.notify(ServerMsg::ChatMsg(msg.clone())); + if g == group { + client.notify(ServerMsg::ChatMsg(resolved_msg.clone())); } } }, diff --git a/server/src/sys/message.rs b/server/src/sys/message.rs index d2d7c895d6..759c469004 100644 --- a/server/src/sys/message.rs +++ b/server/src/sys/message.rs @@ -5,7 +5,7 @@ use crate::{ }; use common::{ comp::{ - Admin, AdminList, CanBuild, ChatMode, ChatMsg, ChatType, ControlEvent, Controller, + Admin, AdminList, CanBuild, ChatMode, UnresolvedChatMsg, ChatType, ControlEvent, Controller, ForceUpdate, Ori, Player, Pos, Stats, Vel, }, event::{EventBus, ServerEvent}, @@ -32,7 +32,7 @@ impl Sys { #[allow(clippy::too_many_arguments)] async fn handle_client_msg( server_emitter: &mut common::event::Emitter<'_, ServerEvent>, - new_chat_msgs: &mut Vec<(Option, ChatMsg)>, + new_chat_msgs: &mut Vec<(Option, UnresolvedChatMsg)>, player_list: &HashMap, new_players: &mut Vec, entity: specs::Entity, @@ -202,7 +202,7 @@ impl Sys { // Only send login message if it wasn't already // sent previously if !client.login_msg_sent { - new_chat_msgs.push((None, ChatMsg { + new_chat_msgs.push((None, UnresolvedChatMsg { chat_type: ChatType::Online, message: format!("[{}] is now online.", &player.alias), // TODO: Localize this })); @@ -461,7 +461,7 @@ impl<'a> System<'a> for Sys { let mut server_emitter = server_event_bus.emitter(); - let mut new_chat_msgs: Vec<(Option, ChatMsg)> = Vec::new(); + let mut new_chat_msgs = Vec::new(); // Player list to send new players. let player_list = (&uids, &players, stats.maybe(), admins.maybe()) diff --git a/voxygen/src/hud/chat.rs b/voxygen/src/hud/chat.rs index 46267014ad..7b5ac61548 100644 --- a/voxygen/src/hud/chat.rs +++ b/voxygen/src/hud/chat.rs @@ -475,7 +475,7 @@ fn cursor_offset_to_index( } /// Get the color and icon for the current line in the chat box -fn render_chat_line(chat_type: &ChatType, imgs: &Imgs) -> (Color, conrod_core::image::Id) { +fn render_chat_line(chat_type: &ChatType, imgs: &Imgs) -> (Color, conrod_core::image::Id) { match chat_type { ChatType::Online => (ONLINE_COLOR, imgs.chat_online_small), ChatType::Offline => (OFFLINE_COLOR, imgs.chat_offline_small),