Re-implement names in chat. It is done client-side now and /alias changes are retroactive.

This commit is contained in:
CapsizeGlimmer 2020-06-02 02:11:47 -04:00 committed by Forest Anderson
parent b08d717eac
commit 0b2a3ebe8b
9 changed files with 127 additions and 66 deletions

View File

@ -66,7 +66,7 @@ pub struct Client {
thread_pool: ThreadPool,
pub server_info: ServerInfo,
pub world_map: (Arc<DynamicImage>, Vec2<u32>),
pub player_list: HashMap<u64, PlayerInfo>,
pub player_list: HashMap<Uid, PlayerInfo>,
pub character_list: CharacterList,
pub active_character_id: Option<i32>,
@ -759,6 +759,16 @@ impl Client {
);
}
},
ServerMsg::PlayerListUpdate(PlayerListUpdate::Admin(uid, admin)) => {
if let Some(player_info) = self.player_list.get_mut(&uid) {
player_info.is_admin = admin;
} else {
warn!(
"Received msg to update admin status of uid {}, but they were not in the list.",
uid
);
}
},
ServerMsg::PlayerListUpdate(PlayerListUpdate::SelectedCharacter(
uid,
char_info,
@ -822,7 +832,7 @@ impl Client {
},
ServerMsg::ChatMsg(m) => frontend_events.push(Event::Chat(m)),
ServerMsg::SetPlayerEntity(uid) => {
if let Some(entity) = self.state.ecs().entity_from_uid(uid) {
if let Some(entity) = self.state.ecs().entity_from_uid(uid.0) {
self.entity = entity;
} else {
return Err(Error::Other("Failed to find entity from uid.".to_owned()));
@ -853,7 +863,7 @@ impl Client {
{
self.state
.ecs_mut()
.delete_entity_and_clear_from_uid_allocator(entity);
.delete_entity_and_clear_from_uid_allocator(entity.0);
}
},
// Cleanup for when the client goes back to the `Registered` state

View File

@ -2,6 +2,7 @@ use super::{ClientState, EcsCompPacket};
use crate::{
character::CharacterItem,
comp, state, sync,
sync::Uid,
terrain::{Block, TerrainChunk},
};
use authc::AuthClientError;
@ -17,18 +18,21 @@ pub struct ServerInfo {
pub auth_provider: Option<String>,
}
/// Inform the client of updates to the player list.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum PlayerListUpdate {
Init(HashMap<u64, PlayerInfo>),
Add(u64, PlayerInfo),
SelectedCharacter(u64, CharacterInfo),
LevelChange(u64, u32),
Remove(u64),
Alias(u64, String),
Init(HashMap<Uid, PlayerInfo>),
Add(Uid, PlayerInfo),
SelectedCharacter(Uid, CharacterInfo),
LevelChange(Uid, u32),
Admin(Uid, bool),
Remove(Uid),
Alias(Uid, String),
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PlayerInfo {
pub is_admin: bool,
pub player_alias: String,
pub character: Option<CharacterInfo>,
}
@ -69,12 +73,12 @@ pub enum ServerMsg {
/// A message to go into the client chat box. The client is responsible for
/// formatting the message.
ChatMsg(comp::ChatMsg),
SetPlayerEntity(u64),
SetPlayerEntity(Uid),
TimeOfDay(state::TimeOfDay),
EntitySync(sync::EntitySyncPackage),
CompSync(sync::CompSyncPackage<EcsCompPacket>),
CreateEntity(sync::EntityPackage<EcsCompPacket>),
DeleteEntity(u64),
DeleteEntity(Uid),
InventoryUpdate(comp::Inventory, comp::InventoryUpdateEvent),
TerrainChunkUpdate {
key: Vec2<i32>,
@ -112,7 +116,8 @@ impl From<AuthClientError> for RegisterError {
}
impl ServerMsg {
/// Sends either say, world, group, etc. based on the player's current chat mode.
/// 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))
}

View File

@ -388,7 +388,20 @@ fn handle_alias(
args: String,
action: &ChatCommand,
) {
if client != target {
// Prevent people abusing /sudo
server.notify_client(
client,
ServerMsg::private(String::from("Don't call people names. It's mean.")),
);
return;
}
if let Ok(alias) = scan_fmt!(&args, &action.arg_fmt(), String) {
if !comp::Player::alias_is_valid(&alias) {
// Prevent silly aliases
server.notify_client(client, ServerMsg::private(String::from("Invalid alias.")));
return;
}
let old_alias_optional = server
.state
.ecs_mut()
@ -932,26 +945,30 @@ fn handle_adminify(
let ecs = server.state.ecs();
let opt_player = (&ecs.entities(), &ecs.read_storage::<comp::Player>())
.join()
.find(|(_, player)| player.alias == alias)
.find(|(_, player)| alias == player.alias)
.map(|(entity, _)| entity);
match opt_player {
Some(player) => match server.state.read_component_cloned::<comp::Admin>(player) {
Some(_admin) => {
Some(player) => {
let is_admin = if server.state.read_component_cloned::<comp::Admin>(player).is_some() {
ecs.write_storage::<comp::Admin>().remove(player);
},
None => {
server.state.write_component(player, comp::Admin);
},
false
} else {
ecs.write_storage().insert(player, comp::Admin).is_ok()
};
// Update player list so the player shows up as admin in client chat.
let msg = ServerMsg::PlayerListUpdate(PlayerListUpdate::Admin(
*ecs.read_storage::<Uid>()
.get(player)
.expect("Player should have uid"),
is_admin,
));
server.state.notify_registered_clients(msg);
},
None => {
server.notify_client(
client,
ServerMsg::private(format!("Player '{}' not found!", alias)),
);
server.notify_client(
client,
ServerMsg::private(String::from(action.help_string())),
);
},
}
} else {
@ -1319,13 +1336,12 @@ fn handle_set_level(
match target {
Ok(player) => {
let uid = server
let uid = *server
.state
.ecs()
.read_storage::<Uid>()
.get(player)
.expect("Failed to get uid for player")
.0;
.expect("Failed to get uid for player");
server
.state
.notify_registered_clients(ServerMsg::PlayerListUpdate(

View File

@ -471,12 +471,11 @@ pub fn handle_level_up(server: &mut Server, entity: EcsEntity, new_level: u32) {
let uids = server.state.ecs().read_storage::<Uid>();
let uid = uids
.get(entity)
.expect("Failed to fetch uid component for entity.")
.0;
.expect("Failed to fetch uid component for entity.");
server
.state
.notify_registered_clients(ServerMsg::PlayerListUpdate(PlayerListUpdate::LevelChange(
uid, new_level,
*uid, new_level,
)));
}

View File

@ -301,7 +301,7 @@ impl<'a> System<'a> for Sys {
})
{
for uid in &deleted {
client.notify(ServerMsg::DeleteEntity(*uid));
client.notify(ServerMsg::DeleteEntity(Uid(*uid)));
}
}
}

View File

@ -5,7 +5,8 @@ use crate::{
};
use common::{
comp::{
CanBuild, ChatMode, ControlEvent, Controller, ForceUpdate, Ori, Player, Pos, Stats, Vel,
Admin, CanBuild, ChatMode, ControlEvent, Controller, ForceUpdate, Ori, Player, Pos, Stats,
Vel,
},
event::{EventBus, ServerEvent},
msg::{
@ -33,6 +34,7 @@ impl<'a> System<'a> for Sys {
ReadExpect<'a, CharacterLoader>,
ReadExpect<'a, TerrainGrid>,
Write<'a, SysTimer<Self>>,
ReadStorage<'a, Admin>,
ReadStorage<'a, Uid>,
ReadStorage<'a, CanBuild>,
ReadStorage<'a, ForceUpdate>,
@ -62,6 +64,7 @@ impl<'a> System<'a> for Sys {
character_loader,
terrain,
mut timer,
admins,
uids,
can_build,
force_updates,
@ -86,13 +89,13 @@ impl<'a> System<'a> for Sys {
let mut new_chat_msgs = Vec::new();
// Player list to send new players.
let player_list = (&uids, &players, &stats)
let player_list = (&uids, &players, stats.maybe(), admins.maybe())
.join()
.map(|(uid, player, stats)| {
.map(|(uid, player, stats, admin)| {
((*uid).into(), PlayerInfo {
is_admin: admin.is_some(),
player_alias: player.alias.clone(),
// TODO: player might not have a character selected
character: Some(CharacterInfo {
character: stats.map(|stats| CharacterInfo {
name: stats.name.clone(),
level: stats.level.level(),
}),
@ -441,6 +444,7 @@ impl<'a> System<'a> for Sys {
let msg =
ServerMsg::PlayerListUpdate(PlayerListUpdate::Add((*uid).into(), PlayerInfo {
player_alias: player.alias.clone(),
is_admin: admins.get(entity).is_some(),
character: None, // new players will be on character select.
}));
for client in (&mut clients).join().filter(|c| c.is_registered()) {

View File

@ -169,7 +169,7 @@ impl<'a> System<'a> for Sys {
.iter()
.flat_map(|v| v.iter())
{
client.notify(ServerMsg::DeleteEntity(*uid));
client.notify(ServerMsg::DeleteEntity(Uid(*uid)));
}
}

View File

@ -18,6 +18,7 @@ use conrod_core::{
widget::{self, Button, Id, List, Rectangle, Text, TextEdit},
widget_ids, Colorable, Positionable, Sizeable, Ui, UiCell, Widget, WidgetCommon,
};
use specs::world::WorldExt;
use std::collections::VecDeque;
widget_ids! {
@ -36,6 +37,7 @@ const MAX_MESSAGES: usize = 100;
#[derive(WidgetCommon)]
pub struct Chat<'a> {
new_messages: &'a mut VecDeque<ChatMsg>,
client: &'a Client,
force_input: Option<String>,
force_cursor: Option<Index>,
force_completions: Option<Vec<String>>,
@ -54,12 +56,14 @@ pub struct Chat<'a> {
impl<'a> Chat<'a> {
pub fn new(
new_messages: &'a mut VecDeque<ChatMsg>,
client: &'a Client,
global_state: &'a GlobalState,
imgs: &'a Imgs,
fonts: &'a ConrodVoxygenFonts,
) -> Self {
Self {
new_messages,
client,
force_input: None,
force_cursor: None,
force_completions: None,
@ -71,9 +75,9 @@ impl<'a> Chat<'a> {
}
}
pub fn prepare_tab_completion(mut self, input: String, client: &Client) -> Self {
pub fn prepare_tab_completion(mut self, input: String) -> Self {
if let Some(index) = input.find('\t') {
self.force_completions = Some(cmd::complete(&input[..index], &client));
self.force_completions = Some(cmd::complete(&input[..index], &self.client));
} else {
self.force_completions = None;
}
@ -305,6 +309,19 @@ impl<'a> Widget for Chat<'a> {
}
}
let alias_of_uid = |uid| {
self.client
.player_list
.get(uid)
.map_or("<?>".to_string(), |player_info| {
if player_info.is_admin {
format!("ADMIN - {}", player_info.player_alias)
} else {
player_info.player_alias.to_string()
}
})
};
let message_format = |uid, message| format!("[{}]: {}", alias_of_uid(uid), message);
// Message box
Rectangle::fill([470.0, 174.0])
.rgba(0.0, 0.0, 0.0, transp)
@ -323,20 +340,35 @@ impl<'a> Widget for Chat<'a> {
.set(state.ids.message_box, ui);
while let Some(item) = items.next(ui) {
// This would be easier if conrod used the v-metrics from rusttype.
let widget = if item.i < state.messages.len() {
let msg = &state.messages[item.i];
let color = match msg.chat_type {
ChatType::Tell(_, _) => TELL_COLOR,
ChatType::Private => PRIVATE_COLOR,
ChatType::Broadcast => BROADCAST_COLOR,
ChatType::Say(_) => SAY_COLOR,
ChatType::Group(_) => GROUP_COLOR,
ChatType::Faction(_) => FACTION_COLOR,
ChatType::Region(_) => REGION_COLOR,
ChatType::Kill => KILL_COLOR,
ChatType::World(_) => WORLD_COLOR,
if item.i < state.messages.len() {
let ChatMsg { chat_type, message } = &state.messages[item.i];
let (color, msg) = match chat_type {
ChatType::Private => (PRIVATE_COLOR, message.to_string()),
ChatType::Broadcast => (BROADCAST_COLOR, message.to_string()),
ChatType::Kill => (KILL_COLOR, message.to_string()),
ChatType::Tell(from, to) => {
let from_alias = alias_of_uid(&from);
let to_alias = alias_of_uid(&to);
if Some(from)
== self
.client
.state()
.ecs()
.read_storage()
.get(self.client.entity())
{
(TELL_COLOR, format!("To [{}]: {}", to_alias, message))
} else {
(TELL_COLOR, format!("From [{}]: {}", from_alias, message))
}
},
ChatType::Say(uid) => (SAY_COLOR, message_format(uid, message)),
ChatType::Group(uid) => (GROUP_COLOR, message_format(uid, message)),
ChatType::Faction(uid) => (FACTION_COLOR, message_format(uid, message)),
ChatType::Region(uid) => (REGION_COLOR, message_format(uid, message)),
ChatType::World(uid) => (WORLD_COLOR, message_format(uid, message)),
};
let text = Text::new(&msg.message)
let text = Text::new(&msg)
.font_size(self.fonts.opensans.scale(15))
.font_id(self.fonts.opensans.conrod_id)
.w(470.0)
@ -347,23 +379,17 @@ impl<'a> Widget for Chat<'a> {
Dimension::Absolute(y) => y + 2.0,
_ => 0.0,
};
Some(text.h(y))
let widget = text.h(y);
item.set(widget, ui);
} else {
// Spacer at bottom of the last message so that it is not cut off.
// Needs to be larger than the space above.
Some(
Text::new("")
.font_size(self.fonts.opensans.scale(6))
.font_id(self.fonts.opensans.conrod_id)
.w(470.0),
)
let widget = Text::new("")
.font_size(self.fonts.opensans.scale(6))
.font_id(self.fonts.opensans.conrod_id)
.w(470.0);
item.set(widget, ui);
};
match widget {
Some(widget) => {
item.set(widget, ui);
},
None => {},
}
}
// Chat Arrow

View File

@ -81,7 +81,7 @@ const PRIVATE_COLOR: Color = Color::Rgba(1.0, 1.0, 0.0, 1.0);
/// Color for public messages from the server
const BROADCAST_COLOR: Color = Color::Rgba(0.28, 0.83, 0.71, 1.0);
/// Color for local chat
const SAY_COLOR: Color = Color::Rgba(0.9, 0.2, 0.2, 1.0);
const SAY_COLOR: Color = Color::Rgba(1.0, 0.8, 0.8, 1.0);
/// Color for group chat
const GROUP_COLOR: Color = Color::Rgba(0.47, 0.84, 1.0, 1.0);
/// Color for factional chat
@ -1554,13 +1554,14 @@ impl Hud {
// Chat box
match Chat::new(
&mut self.new_messages,
&client,
global_state,
&self.imgs,
&self.fonts,
)
.and_then(self.force_chat_input.take(), |c, input| c.input(input))
.and_then(self.tab_complete.take(), |c, input| {
c.prepare_tab_completion(input, &client)
c.prepare_tab_completion(input)
})
.and_then(self.force_chat_cursor.take(), |c, pos| c.cursor_pos(pos))
.set(self.ids.chat, ui_widgets)