Implement chat filtering for /say /region /group etc.

This commit is contained in:
CapsizeGlimmer 2020-06-04 21:48:26 -04:00 committed by Forest Anderson
parent 5cbecb29e6
commit c984bdcdf1
8 changed files with 196 additions and 82 deletions

View File

@ -1,4 +1,4 @@
use crate::path::Chaser;
use crate::{path::Chaser, state::Time};
use specs::{Component, Entity as EcsEntity};
use specs_idvs::IDVStorage;
use vek::*;
@ -128,9 +128,6 @@ pub struct SpeechBubble {
pub timeout: Option<Time>,
// TODO add icon enum for player chat type / npc quest+trade
}
impl Component for SpeechBubble {
type Storage = FlaggedStorage<Self, HashMapStorage<Self>>;
}
impl SpeechBubble {
pub fn npc_new(i18n_key: String, now: Time) -> Self {
let message = SpeechBubbleMessage::Localized(i18n_key, rand::random());

View File

@ -26,7 +26,7 @@ impl Component for ChatMode {
impl ChatMode {
/// Create a message from your current chat mode and uuid.
pub fn msg_from(&self, from: Uid, message: String) -> ChatMsg {
pub fn new_message(&self, from: Uid, message: String) -> ChatMsg {
let chat_type = match self {
ChatMode::Tell(to) => ChatType::Tell(from, *to),
ChatMode::Say => ChatType::Say(from),
@ -79,6 +79,15 @@ pub struct ChatMsg {
}
impl ChatMsg {
pub const NPC_DISTANCE: f32 = 100.0;
pub const REGION_DISTANCE: f32 = 1000.0;
pub const SAY_DISTANCE: f32 = 100.0;
pub fn broadcast(message: String) -> Self {
let chat_type = ChatType::Broadcast;
Self { chat_type, message }
}
pub fn npc(uid: Uid, message: String) -> Self {
let chat_type = ChatType::Npc(uid, rand::random());
Self { chat_type, message }
@ -105,6 +114,21 @@ impl ChatMsg {
}
})
}
pub fn uid(&self) -> Option<Uid> {
match &self.chat_type {
ChatType::Broadcast => None,
ChatType::Private => 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),
ChatType::Npc(u, _r) => Some(*u),
}
}
}
/// Player groups are useful when forming raiding parties and coordinating

View File

@ -116,12 +116,6 @@ impl From<AuthClientError> for RegisterError {
}
impl ServerMsg {
/// Sends either say, world, group, etc. based on the player's current chat
/// mode.
pub fn chat(mode: comp::ChatMode, uid: sync::Uid, message: String) -> ServerMsg {
ServerMsg::ChatMsg(mode.msg_from(uid, message))
}
pub fn broadcast(message: String) -> ServerMsg {
ServerMsg::ChatMsg(comp::ChatMsg {
chat_type: comp::ChatType::Broadcast,

View File

@ -1033,11 +1033,7 @@ fn handle_tell(
} else {
msg.to_string()
};
server.notify_client(
player,
ServerMsg::chat(mode.clone(), client_uid, msg.clone()),
);
server.notify_client(client, ServerMsg::chat(mode, client_uid, msg));
server.state.send_chat(mode.new_message(client_uid, msg));
} else {
server.notify_client(
client,
@ -1073,11 +1069,9 @@ fn handle_faction(
let _ = ecs.write_storage().insert(client, mode.clone());
if !msg.is_empty() {
if let Some(uid) = ecs.read_storage().get(client) {
server.state.notify_registered_clients(ServerMsg::chat(
mode,
*uid,
msg.to_string(),
));
server
.state
.send_chat(mode.new_message(*uid, msg.to_string()));
}
}
} else {
@ -1109,11 +1103,9 @@ fn handle_group(
let _ = ecs.write_storage().insert(client, mode.clone());
if !msg.is_empty() {
if let Some(uid) = ecs.read_storage().get(client) {
server.state.notify_registered_clients(ServerMsg::chat(
mode,
*uid,
msg.to_string(),
));
server
.state
.send_chat(mode.new_message(*uid, msg.to_string()));
}
}
} else {
@ -1149,7 +1141,7 @@ fn handle_region(
if let Some(uid) = server.state.ecs().read_storage().get(client) {
server
.state
.notify_registered_clients(ServerMsg::chat(mode, *uid, msg.to_string()));
.send_chat(mode.new_message(*uid, msg.to_string()));
}
}
}
@ -1179,7 +1171,7 @@ fn handle_say(
if let Some(uid) = server.state.ecs().read_storage().get(client) {
server
.state
.notify_registered_clients(ServerMsg::chat(mode, *uid, msg.to_string()));
.send_chat(mode.new_message(*uid, msg.to_string()));
}
}
}
@ -1209,7 +1201,7 @@ fn handle_world(
if let Some(uid) = server.state.ecs().read_storage().get(client) {
server
.state
.notify_registered_clients(ServerMsg::chat(mode, *uid, msg.to_string()));
.send_chat(mode.new_message(*uid, msg.to_string()));
}
}
}

View File

@ -1,8 +1,5 @@
use crate::{state_ext::StateExt, Server};
use common::{
event::{EventBus, ServerEvent},
msg::ServerMsg,
};
use common::event::{EventBus, ServerEvent};
use entity_creation::{
handle_create_npc, handle_create_waypoint, handle_initialize_character,
handle_loaded_character_data, handle_shoot,
@ -23,16 +20,9 @@ mod inventory_manip;
mod player;
pub enum Event {
ClientConnected {
entity: EcsEntity,
},
ClientDisconnected {
entity: EcsEntity,
},
Chat {
entity: Option<EcsEntity>,
msg: String,
},
ClientConnected { entity: EcsEntity },
ClientDisconnected { entity: EcsEntity },
Chat { entity: Option<EcsEntity>, msg: String },
}
impl Server {
@ -41,6 +31,7 @@ impl Server {
let mut requested_chunks = Vec::new();
let mut chat_commands = Vec::new();
let mut chat_messages = Vec::new();
let events = self
.state
@ -107,8 +98,7 @@ impl Server {
chat_commands.push((entity, cmd));
},
ServerEvent::Chat(msg) => {
self.state
.notify_registered_clients(ServerMsg::ChatMsg(msg));
chat_messages.push(msg);
},
}
}
@ -122,6 +112,10 @@ impl Server {
self.process_chat_cmd(entity, cmd);
}
for msg in chat_messages {
self.state.send_chat(msg);
}
frontend_events
}
}

View File

@ -8,7 +8,7 @@ use common::{
msg::{ClientState, PlayerListUpdate, ServerMsg},
sync::{Uid, UidAllocator},
};
use specs::{saveload::MarkerAllocator, Builder, Entity as EcsEntity, Join, WorldExt};
use specs::{saveload::MarkerAllocator, Builder, Entity as EcsEntity, WorldExt};
use tracing::error;
pub fn handle_exit_ingame(server: &mut Server, entity: EcsEntity) {
@ -60,19 +60,12 @@ pub fn handle_client_disconnect(server: &mut Server, entity: EcsEntity) -> Event
// Make sure to remove the player from the logged in list. (See AuthProvider)
// And send a disconnected message
{
let players = state.ecs().read_storage::<Player>();
if let Some(player) = state.ecs().read_storage::<Player>().get(entity) {
let mut accounts = state.ecs().write_resource::<AuthProvider>();
let mut clients = state.ecs().write_storage::<Client>();
accounts.logout(player.uuid());
if let Some(player) = players.get(entity) {
accounts.logout(player.uuid());
let msg = ServerMsg::broadcast(format!("{} went offline.", &player.alias));
for client in (&mut clients).join().filter(|c| c.is_registered()) {
client.notify(msg.clone());
}
}
let msg = ServerMsg::broadcast(format!("{} went offline.", &player.alias));
state.notify_registered_clients(msg);
}
// Sync the player's character data to the database

View File

@ -7,11 +7,14 @@ use common::{
effect::Effect,
msg::{CharacterInfo, ClientState, PlayerListUpdate, ServerMsg},
state::State,
sync::{Uid, WorldSyncExt},
sync::{Uid, UidAllocator, WorldSyncExt},
util::Dir,
};
use specs::{Builder, Entity as EcsEntity, EntityBuilder as EcsEntityBuilder, Join, WorldExt};
use tracing::warn;
use specs::{
saveload::MarkerAllocator, Builder, Entity as EcsEntity, EntityBuilder as EcsEntityBuilder,
Join, WorldExt,
};
use vek::*;
pub trait StateExt {
@ -43,6 +46,14 @@ 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 create_player_character(
&mut self,
entity: EcsEntity,
character_id: i32,
body: comp::Body,
server_settings: &ServerSettings,
);
fn send_chat(&self, msg: comp::ChatMsg);
fn notify_registered_clients(&self, msg: ServerMsg);
/// Delete an entity, recording the deletion in [`DeletedEntities`]
fn delete_entity_recorded(
@ -229,6 +240,117 @@ impl StateExt for State {
self.write_component(entity, comp::ForceUpdate);
}
/// 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) {
let ecs = self.ecs();
let is_within = |target, a: &comp::Pos, b_opt: Option<&comp::Pos>| {
if let Some(b) = b_opt {
a.0.distance(b.0) < target
} else {
false
}
};
match &msg.chat_type {
comp::ChatType::Broadcast
| comp::ChatType::Kill
| comp::ChatType::Private
| comp::ChatType::World(_) => {
self.notify_registered_clients(ServerMsg::ChatMsg(msg.clone()))
},
comp::ChatType::Tell(u, t) => {
for (client, uid) in (
&mut ecs.write_storage::<Client>(),
&ecs.read_storage::<Uid>(),
)
.join()
{
if uid == u || uid == t {
client.notify(ServerMsg::ChatMsg(msg.clone()));
}
}
},
comp::ChatType::Say(_u) => {
for (client, pos) in (
&mut ecs.write_storage::<Client>(),
&ecs.read_storage::<comp::Pos>(),
)
.join()
{
let entity_opt = msg.uid().and_then(|uid| {
(*ecs.read_resource::<UidAllocator>()).retrieve_entity_internal(uid.0)
});
let positions = ecs.read_storage::<comp::Pos>();
let pos_opt = entity_opt.and_then(|e| positions.get(e));
if is_within(comp::ChatMsg::SAY_DISTANCE, pos, pos_opt) {
client.notify(ServerMsg::ChatMsg(msg.clone()));
}
}
},
comp::ChatType::Region(_u) => {
for (client, pos) in (
&mut ecs.write_storage::<Client>(),
&ecs.read_storage::<comp::Pos>(),
)
.join()
{
let entity_opt = msg.uid().and_then(|uid| {
(*ecs.read_resource::<UidAllocator>()).retrieve_entity_internal(uid.0)
});
let positions = ecs.read_storage::<comp::Pos>();
let pos_opt = entity_opt.and_then(|e| positions.get(e));
if is_within(comp::ChatMsg::REGION_DISTANCE, pos, pos_opt) {
client.notify(ServerMsg::ChatMsg(msg.clone()));
}
}
},
comp::ChatType::Npc(_u, _r) => {
for (client, pos) in (
&mut ecs.write_storage::<Client>(),
&ecs.read_storage::<comp::Pos>(),
)
.join()
{
let entity_opt = msg.uid().and_then(|uid| {
(*ecs.read_resource::<UidAllocator>()).retrieve_entity_internal(uid.0)
});
let positions = ecs.read_storage::<comp::Pos>();
let pos_opt = entity_opt.and_then(|e| positions.get(e));
if is_within(comp::ChatMsg::NPC_DISTANCE, pos, pos_opt) {
client.notify(ServerMsg::ChatMsg(msg.clone()));
}
}
},
comp::ChatType::Faction(_u, s) => {
for (client, faction) in (
&mut ecs.write_storage::<Client>(),
&ecs.read_storage::<comp::Faction>(),
)
.join()
{
if s == &faction.0 {
client.notify(ServerMsg::ChatMsg(msg.clone()));
}
}
},
comp::ChatType::Group(_u, s) => {
for (client, group) in (
&mut ecs.write_storage::<Client>(),
&ecs.read_storage::<comp::Group>(),
)
.join()
{
if s == &group.0 {
client.notify(ServerMsg::ChatMsg(msg.clone()));
}
}
},
}
}
/// Sends the message to all connected clients
fn notify_registered_clients(&self, msg: ServerMsg) {
for client in (&mut self.ecs().write_storage::<Client>())
.join()

View File

@ -5,8 +5,8 @@ use crate::{
};
use common::{
comp::{
Admin, CanBuild, ChatMode, ControlEvent, Controller, ForceUpdate, Ori, Player, Pos, Stats,
Vel,
Admin, CanBuild, ChatMode, ChatMsg, ControlEvent, Controller, ForceUpdate, Ori, Player,
Pos, Stats, Vel,
},
event::{EventBus, ServerEvent},
msg::{
@ -230,7 +230,11 @@ impl<'a> System<'a> for Sys {
// Become Registered first.
ClientState::Connected => client.error_state(RequestStateError::Impossible),
ClientState::Registered | ClientState::Spectator => {
if let Some(player) = players.get(entity) {
// Only send login message if it wasn't already
// sent previously
if let (Some(player), false) =
(players.get(entity), client.login_msg_sent)
{
// Send a request to load the character's component data from the
// DB. Once loaded, persisted components such as stats and inventory
// will be inserted for the entity
@ -240,6 +244,10 @@ impl<'a> System<'a> for Sys {
character_id,
);
server_emitter.emit(ServerEvent::Chat(ChatMsg::broadcast(
format!("[{}] is now online.", &player.alias),
)));
// Start inserting non-persisted/default components for the entity
// while we load the DB data
server_emitter.emit(ServerEvent::InitCharacterData {
@ -332,7 +340,7 @@ impl<'a> System<'a> for Sys {
.get(entity)
.map(Clone::clone)
.unwrap_or(ChatMode::default());
let msg = ServerMsg::chat(mode, *from, message);
let msg = mode.new_message(*from, message);
new_chat_msgs.push((Some(entity), msg));
} else {
tracing::error!("Could not send message. Missing player uid");
@ -458,25 +466,15 @@ impl<'a> System<'a> for Sys {
// Handle new chat messages.
for (entity, msg) in new_chat_msgs {
match msg {
ServerMsg::ChatMsg(msg) => {
// Handle chat commands.
if msg.message.starts_with("/") {
if let (Some(entity), true) = (entity, msg.message.len() > 1) {
let argv = String::from(&msg.message[1..]);
server_emitter.emit(ServerEvent::ChatCmd(entity, argv));
}
} else {
// Send speech bubble and chat message
// TODO filter group, faction, say, and bubble distance.
for client in (&mut clients).join().filter(|c| c.is_registered()) {
client.notify(ServerMsg::ChatMsg(msg.clone()));
}
}
},
_ => {
panic!("Invalid message type.");
},
// Handle chat commands.
if msg.message.starts_with("/") {
if let (Some(entity), true) = (entity, msg.message.len() > 1) {
let argv = String::from(&msg.message[1..]);
server_emitter.emit(ServerEvent::ChatCmd(entity, argv));
}
} else {
// Send chat message
server_emitter.emit(ServerEvent::Chat(msg));
}
}