Merge branch 'scott-c/improve-chat-aliases' into 'master'

fix #562 Confusing chat alias

Closes #562

See merge request veloren/veloren!1006
This commit is contained in:
Monty Marz 2020-06-01 16:26:22 +00:00
commit 43dc2322bf
13 changed files with 191 additions and 59 deletions

View File

@ -15,6 +15,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Speech bubbles appear when nearby players talk
- NPCs call for help when attacked
- Eyebrows and shapes can now be selected
- Character name and level information to chat, social tab and `/players` command.
### Changed

View File

@ -40,6 +40,7 @@ fn complete_player(part: &str, client: &Client) -> Vec<String> {
client
.player_list
.values()
.map(|player_info| &player_info.player_alias)
.filter(|alias| alias.starts_with(part))
.cloned()
.collect()

View File

@ -23,7 +23,7 @@ use common::{
event::{EventBus, SfxEvent, SfxEventItem},
msg::{
validate_chat_msg, ChatMsgValidationError, ClientMsg, ClientState, Notification,
PlayerListUpdate, RegisterError, RequestStateError, ServerInfo, ServerMsg,
PlayerInfo, PlayerListUpdate, RegisterError, RequestStateError, ServerInfo, ServerMsg,
MAX_BYTES_CHAT_MSG,
},
net::PostBox,
@ -68,7 +68,7 @@ pub struct Client {
thread_pool: ThreadPool,
pub server_info: ServerInfo,
pub world_map: (Arc<DynamicImage>, Vec2<u32>),
pub player_list: HashMap<u64, String>,
pub player_list: HashMap<u64, PlayerInfo>,
pub character_list: CharacterList,
postbox: PostBox<ClientMsg, ServerMsg>,
@ -734,16 +734,51 @@ impl Client {
ServerMsg::PlayerListUpdate(PlayerListUpdate::Init(list)) => {
self.player_list = list
},
ServerMsg::PlayerListUpdate(PlayerListUpdate::Add(uid, name)) => {
if let Some(old_name) = self.player_list.insert(uid, name.clone()) {
ServerMsg::PlayerListUpdate(PlayerListUpdate::Add(uid, player_info)) => {
if let Some(old_player_info) =
self.player_list.insert(uid, player_info.clone())
{
warn!(
"Received msg to insert {} with uid {} into the player list but \
there was already an entry for {} with the same uid that was \
overwritten!",
name, uid, old_name
player_info.player_alias, uid, old_player_info.player_alias
);
}
},
ServerMsg::PlayerListUpdate(PlayerListUpdate::SelectedCharacter(
uid,
char_info,
)) => {
if let Some(player_info) = self.player_list.get_mut(&uid) {
player_info.character = Some(char_info);
} else {
warn!(
"Received msg to update character info for uid {}, but they were \
not in the list.",
uid
);
}
},
ServerMsg::PlayerListUpdate(PlayerListUpdate::LevelChange(uid, next_level)) => {
if let Some(player_info) = self.player_list.get_mut(&uid) {
player_info.character = match &player_info.character {
Some(character) => Some(common::msg::CharacterInfo {
name: character.name.to_string(),
level: next_level,
}),
None => {
warn!(
"Received msg to update character level info to {} for \
uid {}, but this player's character is None.",
next_level, uid
);
None
},
};
}
},
ServerMsg::PlayerListUpdate(PlayerListUpdate::Remove(uid)) => {
if self.player_list.remove(&uid).is_none() {
warn!(
@ -754,8 +789,8 @@ impl Client {
}
},
ServerMsg::PlayerListUpdate(PlayerListUpdate::Alias(uid, new_name)) => {
if let Some(name) = self.player_list.get_mut(&uid) {
*name = new_name;
if let Some(player_info) = self.player_list.get_mut(&uid) {
player_info.player_alias = new_name;
} else {
warn!(
"Received msg to alias player with uid {} to {} but this uid is \

View File

@ -90,6 +90,7 @@ pub enum ServerEvent {
Mount(EcsEntity, EcsEntity),
Unmount(EcsEntity),
Possess(Uid, Uid),
LevelUp(EcsEntity, u32),
SelectCharacter {
entity: EcsEntity,
character_id: i32,

View File

@ -7,7 +7,8 @@ pub use self::{
client::ClientMsg,
ecs_packet::EcsCompPacket,
server::{
Notification, PlayerListUpdate, RegisterError, RequestStateError, ServerInfo, ServerMsg,
CharacterInfo, Notification, PlayerInfo, PlayerListUpdate, RegisterError,
RequestStateError, ServerInfo, ServerMsg,
},
};

View File

@ -20,12 +20,26 @@ pub struct ServerInfo {
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum PlayerListUpdate {
Init(HashMap<u64, String>),
Add(u64, String),
Init(HashMap<u64, PlayerInfo>),
Add(u64, PlayerInfo),
SelectedCharacter(u64, CharacterInfo),
LevelChange(u64, u32),
Remove(u64),
Alias(u64, String),
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PlayerInfo {
pub player_alias: String,
pub character: Option<CharacterInfo>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CharacterInfo {
pub name: String,
pub level: u32,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum Notification {
WaypointSaved,

View File

@ -66,6 +66,7 @@ impl<'a> System<'a> for Sys {
stat.exp.change_by(-(stat.exp.maximum() as i64));
stat.level.change_by(1);
stat.exp.update_maximum(stat.level.level());
server_event_emitter.emit(ServerEvent::LevelUp(entity, stat.level.level()));
}
stat.update_max_hp();

View File

@ -514,26 +514,28 @@ fn handle_players(
_action: &ChatCommand,
) {
let ecs = server.state.ecs();
let players = ecs.read_storage::<comp::Player>();
let count = players.join().count();
let header_message: String = format!("{} online players: \n", count);
if count > 0 {
let mut player_iter = players.join();
let first = player_iter
.next()
.expect("Player iterator returned none.")
.alias
.to_owned();
let player_list = player_iter.fold(first, |mut s, p| {
s += ",\n";
s += &p.alias;
s
});
server.notify_client(client, ServerMsg::private(header_message + &player_list));
} else {
server.notify_client(client, ServerMsg::private(header_message));
}
let entity_tuples = (
&ecs.entities(),
&ecs.read_storage::<comp::Player>(),
&ecs.read_storage::<comp::Stats>(),
);
server.notify_client(
client,
ServerMsg::private(entity_tuples.join().fold(
format!("{} online players:", entity_tuples.join().count()),
|s, (_, player, stat)| {
format!(
"{}\n[{}]{} Lvl {}",
s,
player.alias,
stat.name,
stat.level.level()
)
},
)),
);
}
fn handle_build(
@ -1125,14 +1127,31 @@ fn handle_set_level(
let (a_lvl, a_alias) = scan_fmt_some!(&args, &action.arg_fmt(), u32, String);
if let Some(lvl) = a_lvl {
let ecs = server.state.ecs_mut();
let target = find_target(&ecs, a_alias, target);
let target = find_target(&server.state.ecs(), a_alias, target);
let mut error_msg = None;
match target {
Ok(player) => {
if let Some(stats) = ecs.write_storage::<comp::Stats>().get_mut(player) {
let uid = server
.state
.ecs()
.read_storage::<Uid>()
.get(player)
.expect("Failed to get uid for player")
.0;
server
.state
.notify_registered_clients(ServerMsg::PlayerListUpdate(
PlayerListUpdate::LevelChange(uid, lvl),
));
if let Some(stats) = server
.state
.ecs_mut()
.write_storage::<comp::Stats>()
.get_mut(player)
{
stats.level.set_level(lvl);
stats.update_max_hp();

View File

@ -2,7 +2,7 @@ use crate::{client::Client, Server, SpawnPoint, StateExt};
use common::{
assets,
comp::{self, object, Body, HealthChange, HealthSource, Item, Player, Stats},
msg::ServerMsg,
msg::{PlayerListUpdate, ServerMsg},
state::BlockChange,
sync::{Uid, WorldSyncExt},
sys::combat::{BLOCK_ANGLE, BLOCK_EFFICIENCY},
@ -295,3 +295,17 @@ pub fn handle_explosion(server: &Server, pos: Vec3<f32>, power: f32, owner: Opti
.cast();
}
}
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;
server
.state
.notify_registered_clients(ServerMsg::PlayerListUpdate(PlayerListUpdate::LevelChange(
uid, new_level,
)));
}

View File

@ -4,7 +4,8 @@ use entity_creation::{
handle_create_character, handle_create_npc, handle_create_waypoint, handle_shoot,
};
use entity_manipulation::{
handle_damage, handle_destroy, handle_explosion, handle_land_on_ground, handle_respawn,
handle_damage, handle_destroy, handle_explosion, handle_land_on_ground, handle_level_up,
handle_respawn,
};
use interaction::{handle_lantern, handle_mount, handle_possess, handle_unmount};
use inventory_manip::handle_inventory;
@ -75,6 +76,7 @@ impl Server {
body,
main,
} => handle_create_character(self, entity, character_id, body, main),
ServerEvent::LevelUp(entity, new_level) => handle_level_up(self, entity, new_level),
ServerEvent::ExitIngame { entity } => handle_exit_ingame(self, entity),
ServerEvent::CreateNpc {
pos,

View File

@ -6,7 +6,9 @@ use common::{
assets,
comp::{self, item},
effect::Effect,
msg::{ClientState, RegisterError, RequestStateError, ServerMsg},
msg::{
CharacterInfo, ClientState, PlayerListUpdate, RegisterError, RequestStateError, ServerMsg,
},
state::State,
sync::{Uid, WorldSyncExt},
util::Dir,
@ -279,6 +281,24 @@ impl StateExt for State {
self.write_component(entity, comp::Admin);
}
let uids = &self.ecs().read_storage::<Uid>();
let uid = uids
.get(entity)
.expect("Failed to fetch uid component for entity.")
.0;
let stats = &self.ecs().read_storage::<comp::Stats>();
let stat = stats
.get(entity)
.expect("Failed to fetch stats component for entity.");
self.notify_registered_clients(ServerMsg::PlayerListUpdate(
PlayerListUpdate::SelectedCharacter(uid, CharacterInfo {
name: stat.name.to_string(),
level: stat.level.level(),
}),
));
// Tell the client its request was successful.
if let Some(client) = self.ecs().write_storage::<Client>().get_mut(entity) {
client.allow_state(ClientState::Character);

View File

@ -10,8 +10,8 @@ use common::{
},
event::{EventBus, ServerEvent},
msg::{
validate_chat_msg, ChatMsgValidationError, ClientMsg, ClientState, PlayerListUpdate,
RequestStateError, ServerMsg, MAX_BYTES_CHAT_MSG,
validate_chat_msg, CharacterInfo, ChatMsgValidationError, ClientMsg, ClientState,
PlayerInfo, PlayerListUpdate, RequestStateError, ServerMsg, MAX_BYTES_CHAT_MSG,
},
state::{BlockChange, Time},
sync::Uid,
@ -83,9 +83,18 @@ impl<'a> System<'a> for Sys {
let mut new_chat_msgs = Vec::new();
// Player list to send new players.
let player_list = (&uids, &players)
let player_list = (&uids, &players, &stats)
.join()
.map(|(uid, player)| ((*uid).into(), player.alias.clone()))
.map(|(uid, player, stats)| {
((*uid).into(), PlayerInfo {
player_alias: player.alias.clone(),
// TODO: player might not have a character selected
character: Some(CharacterInfo {
name: stats.name.clone(),
level: stats.level.level(),
}),
})
})
.collect::<HashMap<_, _>>();
// List of new players to update player lists of all clients.
let mut new_players = Vec::new();
@ -383,10 +392,11 @@ impl<'a> System<'a> for Sys {
// Tell all clients to add them to the player list.
for entity in new_players {
if let (Some(uid), Some(player)) = (uids.get(entity), players.get(entity)) {
let msg = ServerMsg::PlayerListUpdate(PlayerListUpdate::Add(
(*uid).into(),
player.alias.clone(),
));
let msg =
ServerMsg::PlayerListUpdate(PlayerListUpdate::Add((*uid).into(), PlayerInfo {
player_alias: player.alias.clone(),
character: None, // new players will be on character select.
}));
for client in (&mut clients).join().filter(|c| c.is_registered()) {
client.notify(msg.clone())
}
@ -406,16 +416,22 @@ impl<'a> System<'a> for Sys {
} else {
let bubble = SpeechBubble::player_new(message.clone(), *time);
let _ = speech_bubbles.insert(entity, bubble);
match players.get(entity) {
Some(player) => {
if admins.get(entity).is_some() {
format!("[ADMIN][{}] {}", &player.alias, message)
} else {
format!("[{}] {}", &player.alias, message)
}
format!(
"{}[{}] {}: {}",
match admins.get(entity) {
Some(_) => "[ADMIN]",
None => "",
},
None => format!("[<Unknown>] {}", message),
}
match players.get(entity) {
Some(player) => &player.alias,
None => "<Unknown>",
},
match stats.get(entity) {
Some(stat) => &stat.name,
None => "<Unknown>",
},
message
)
}
} else {
message

View File

@ -192,13 +192,20 @@ impl<'a> Widget for Social<'a> {
.font_id(self.fonts.cyri.conrod_id)
.color(TEXT_COLOR)
.set(ids.online_title, ui);
for (i, (_, player_alias)) in self.client.player_list.iter().enumerate() {
Text::new(player_alias)
.down(3.0)
.font_size(self.fonts.cyri.scale(15))
.font_id(self.fonts.cyri.conrod_id)
.color(TEXT_COLOR)
.set(ids.player_names[i], ui);
for (i, (_, player_info)) in self.client.player_list.iter().enumerate() {
Text::new(&format!(
"[{}] {}",
player_info.player_alias,
match &player_info.character {
Some(character) => format!("{} Lvl {}", &character.name, &character.level),
None => "<None>".to_string(), // character select or spectating
}
))
.down(3.0)
.font_size(self.fonts.cyri.scale(15))
.font_id(self.fonts.cyri.conrod_id)
.color(TEXT_COLOR)
.set(ids.player_names[i], ui);
}
}