From d774319cf0c5f17906a3de94f99a1a923286c946 Mon Sep 17 00:00:00 2001 From: Ellen Sun Date: Sun, 22 Jan 2023 18:50:29 -0500 Subject: [PATCH] cleanup and fixes + added change to CHANGELOG.md --- CHANGELOG.md | 1 + common/src/cmd.rs | 17 +++- common/src/comp/admin.rs | 4 +- common/src/event.rs | 2 + server/src/cmd.rs | 43 +++++++++- server/src/events/entity_manipulation.rs | 14 ++++ server/src/events/mod.rs | 11 ++- server/src/sys/msg/register.rs | 4 + voxygen/src/cmd.rs | 100 +++++++++++------------ 9 files changed, 130 insertions(+), 66 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5fdf61f498..52d7b29984 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Command to toggle experimental shaders. - Faster Energy Regeneration while sitting. - Lantern glow for dropped lanterns. +- Suggests commands when an invalid one is entered in chat and added Client-side commands to /help. ### Changed - Bats move slower and use a simple proportional controller to maintain altitude diff --git a/common/src/cmd.rs b/common/src/cmd.rs index 3603562db9..08d1a6741a 100644 --- a/common/src/cmd.rs +++ b/common/src/cmd.rs @@ -246,7 +246,7 @@ pub enum ServerChatCommand { BattleMode, BattleModeForce, Body, - ApplyBuff, + Buff, Build, BuildAreaAdd, BuildAreaList, @@ -269,6 +269,7 @@ pub enum ServerChatCommand { GroupLeave, GroupPromote, Health, + Help, Home, JoinFaction, Jump, @@ -338,7 +339,7 @@ impl ServerChatCommand { "Change your alias", Some(Moderator), ), - ServerChatCommand::ApplyBuff => cmd( + ServerChatCommand::Buff => cmd( vec![ Enum("buff", BUFFS.clone(), Required), Float("strength", 0.01, Optional), @@ -479,6 +480,11 @@ impl ServerChatCommand { "Set your current health", Some(Admin), ), + ServerChatCommand::Help => ChatCommandData::new( + vec![Command(Optional)], + "Display information about commands", + None, + ), ServerChatCommand::Home => cmd(vec![], "Return to the home town", Some(Moderator)), ServerChatCommand::JoinFaction => ChatCommandData::new( vec![Any("faction", Optional)], @@ -728,11 +734,11 @@ impl ServerChatCommand { ServerChatCommand::Adminify => "adminify", ServerChatCommand::Airship => "airship", ServerChatCommand::Alias => "alias", - ServerChatCommand::ApplyBuff => "buff", ServerChatCommand::Ban => "ban", ServerChatCommand::BattleMode => "battlemode", ServerChatCommand::BattleModeForce => "battlemode_force", ServerChatCommand::Body => "body", + ServerChatCommand::Buff => "buff", ServerChatCommand::Build => "build", ServerChatCommand::BuildAreaAdd => "build_area_add", ServerChatCommand::BuildAreaList => "build_area_list", @@ -753,6 +759,7 @@ impl ServerChatCommand { ServerChatCommand::GroupPromote => "group_promote", ServerChatCommand::GroupLeave => "group_leave", ServerChatCommand::Health => "health", + ServerChatCommand::Help => "help", ServerChatCommand::Home => "home", ServerChatCommand::JoinFaction => "join_faction", ServerChatCommand::Jump => "jump", @@ -817,7 +824,9 @@ impl ServerChatCommand { } /// Produce an iterator over all the available commands - pub fn iter() -> impl Iterator + Clone { ::iter() } + pub fn iter() -> impl Iterator + Clone { + ::iter() + } /// A message that explains what the command does pub fn help_string(&self) -> String { diff --git a/common/src/comp/admin.rs b/common/src/comp/admin.rs index a57172950f..2254852f28 100644 --- a/common/src/comp/admin.rs +++ b/common/src/comp/admin.rs @@ -1,6 +1,6 @@ use clap::arg_enum; use serde::{Deserialize, Serialize}; -use specs::{Component, DenseVecStorage, DerefFlaggedStorage}; +use specs::{Component, DerefFlaggedStorage, VecStorage}; arg_enum! { #[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd, Serialize, Deserialize)] @@ -14,5 +14,5 @@ arg_enum! { pub struct Admin(pub AdminRole); impl Component for Admin { - type Storage = DerefFlaggedStorage>; + type Storage = DerefFlaggedStorage>; } diff --git a/common/src/event.rs b/common/src/event.rs index 166483c6a1..d1b52e5c50 100644 --- a/common/src/event.rs +++ b/common/src/event.rs @@ -18,6 +18,7 @@ use crate::{ use serde::{Deserialize, Serialize}; use specs::Entity as EcsEntity; use std::{collections::VecDeque, ops::DerefMut, sync::Mutex}; +use uuid::Uuid; use vek::*; pub type SiteId = u64; @@ -230,6 +231,7 @@ pub enum ServerEvent { MakeAdmin { entity: EcsEntity, admin: comp::Admin, + uuid: Uuid, }, } diff --git a/server/src/cmd.rs b/server/src/cmd.rs index 5c5f321fda..337cd40a51 100644 --- a/server/src/cmd.rs +++ b/server/src/cmd.rs @@ -124,11 +124,11 @@ fn do_command( ServerChatCommand::Adminify => handle_adminify, ServerChatCommand::Airship => handle_spawn_airship, ServerChatCommand::Alias => handle_alias, - ServerChatCommand::ApplyBuff => handle_apply_buff, ServerChatCommand::Ban => handle_ban, ServerChatCommand::BattleMode => handle_battlemode, ServerChatCommand::BattleModeForce => handle_battlemode_force, ServerChatCommand::Body => handle_body, + ServerChatCommand::Buff => handle_buff, ServerChatCommand::Build => handle_build, ServerChatCommand::BuildAreaAdd => handle_build_area_add, ServerChatCommand::BuildAreaList => handle_build_area_list, @@ -149,6 +149,7 @@ fn do_command( ServerChatCommand::GroupLeave => handle_group_leave, ServerChatCommand::GroupPromote => handle_group_promote, ServerChatCommand::Health => handle_health, + ServerChatCommand::Help => handle_help, ServerChatCommand::Home => handle_home, ServerChatCommand::JoinFaction => handle_join_faction, ServerChatCommand::Jump => handle_jump, @@ -1779,6 +1780,44 @@ fn handle_build_area_remove( } } +fn handle_help( + server: &mut Server, + client: EcsEntity, + _target: EcsEntity, + args: Vec, + _action: &ServerChatCommand, +) -> CmdResult<()> { + if let Some(cmd) = parse_cmd_args!(args, ServerChatCommand) { + server.notify_client( + client, + ServerGeneral::server_msg(ChatType::CommandInfo, cmd.help_string()), + ) + } else { + let mut message = String::new(); + let entity_role = server.entity_admin_role(client); + + // Iterate through all commands you have permission to use. + ServerChatCommand::iter() + .filter(|cmd| cmd.needs_role() <= entity_role) + .for_each(|cmd| { + message += &cmd.help_string(); + message += "\n"; + }); + message += "Additionally, you can use the following shortcuts:"; + ServerChatCommand::iter() + .filter_map(|cmd| cmd.short_keyword().map(|k| (k, cmd))) + .for_each(|(k, cmd)| { + message += &format!(" /{} => /{}", k, cmd.keyword()); + }); + + server.notify_client( + client, + ServerGeneral::server_msg(ChatType::CommandInfo, message), + ) + } + Ok(()) +} + fn parse_alignment(owner: Uid, alignment: &str) -> CmdResult { match alignment { "wild" => Ok(Alignment::Wild), @@ -3470,7 +3509,7 @@ fn handle_server_physics( } } -fn handle_apply_buff( +fn handle_buff( server: &mut Server, _client: EcsEntity, target: EcsEntity, diff --git a/server/src/events/entity_manipulation.rs b/server/src/events/entity_manipulation.rs index 2a0e54be99..eb13f2c4ec 100644 --- a/server/src/events/entity_manipulation.rs +++ b/server/src/events/entity_manipulation.rs @@ -11,6 +11,7 @@ use crate::{ sys::terrain::SAFE_ZONE_RADIUS, Server, SpawnPoint, StateExt, }; +use authc::Uuid; use common::{ combat, combat::DamageContributor, @@ -1458,3 +1459,16 @@ pub fn handle_update_map_marker( } } } + +pub fn handle_make_admin(server: &mut Server, entity: EcsEntity, admin: comp::Admin, uuid: Uuid) { + if server + .state + .read_storage::() + .get(entity) + .map_or(false, |player| player.uuid() == uuid) + { + server + .state + .write_component_ignore_entity_dead(entity, admin); + } +} diff --git a/server/src/events/mod.rs b/server/src/events/mod.rs index a070926d2e..b43be72f3c 100644 --- a/server/src/events/mod.rs +++ b/server/src/events/mod.rs @@ -13,7 +13,8 @@ use entity_manipulation::{ handle_aura, handle_bonk, handle_buff, handle_change_ability, handle_combo_change, handle_delete, handle_destroy, handle_energy_change, handle_entity_attacked_hook, handle_explosion, handle_health_change, handle_knockback, handle_land_on_ground, - handle_parry_hook, handle_poise, handle_respawn, handle_teleport_to, handle_update_map_marker, + handle_make_admin, handle_parry_hook, handle_poise, handle_respawn, handle_teleport_to, + handle_update_map_marker, }; use group_manip::handle_group; use information::handle_site_info; @@ -288,9 +289,11 @@ impl Server { ServerEvent::UpdateMapMarker { entity, update } => { handle_update_map_marker(self, entity, update) }, - ServerEvent::MakeAdmin { entity, admin } => { - let _ = self.state.ecs_mut().write_storage().insert(entity, admin); - }, + ServerEvent::MakeAdmin { + entity, + admin, + uuid, + } => handle_make_admin(self, entity, admin, uuid), } } diff --git a/server/src/sys/msg/register.rs b/server/src/sys/msg/register.rs index 8fa399dafd..046eb85b85 100644 --- a/server/src/sys/msg/register.rs +++ b/server/src/sys/msg/register.rs @@ -379,6 +379,7 @@ impl<'a> System<'a> for Sys { .into_values() .map(|(entity, player, admin, msg)| { let username = &player.alias; + let uuid = player.uuid(); info!(?username, "New User"); // Add Player component to this client. // @@ -396,9 +397,12 @@ impl<'a> System<'a> for Sys { // Give the Admin component to the player if their name exists in // admin list if let Some(admin) = admin { + // We need to defer writing to the Admin storage since it's borrowed immutably + // by this system via TrackedStorages. event_bus.emit_now(ServerEvent::MakeAdmin { entity, admin: Admin(admin.role.into()), + uuid, }); } msg diff --git a/voxygen/src/cmd.rs b/voxygen/src/cmd.rs index 322089b2da..3800b79a7e 100644 --- a/voxygen/src/cmd.rs +++ b/voxygen/src/cmd.rs @@ -1,16 +1,12 @@ -extern crate levenshtein; use std::str::FromStr; use crate::{ render::ExperimentalShader, session::settings_change::change_render_mode, GlobalState, }; use client::Client; -use common::{cmd::*, parse_cmd_args, uuid::Uuid}; -use strum::IntoEnumIterator; -use common_net::synced_components::Admin; - -use itertools::Itertools; +use common::{cmd::*, comp::Admin, parse_cmd_args, uuid::Uuid}; use levenshtein::levenshtein; +use strum::IntoEnumIterator; // Please keep this sorted alphabetically, same as with server commands :-) #[derive(Clone, Copy, strum::EnumIter)] @@ -38,7 +34,11 @@ impl ClientChatCommand { "Toggles an experimental shader.", None, ), - ClientChatCommand::Help => cmd(vec![], "", None), + ClientChatCommand::Help => cmd( + vec![Command(Optional)], + "Display information about commands", + None, + ), ClientChatCommand::Mute => cmd( vec![PlayerName(Required)], "Mutes chat messages from a player.", @@ -93,7 +93,9 @@ impl ClientChatCommand { } /// Produce an iterator over all the available commands - pub fn iter() -> impl Iterator + Clone { ::iter() } + pub fn iter() -> impl Iterator + Clone { + ::iter() + } /// Produce an iterator that first goes over all the short keywords /// and their associated commands and then iterates over all the normal @@ -160,12 +162,39 @@ pub fn run_command( Ok(ChatCommandKind::Client(cmd)) => { Ok(Some(run_client_command(client, global_state, cmd, args)?)) }, - Err(()) => { - Ok(Some(handle_invalid_command(client, global_state, vec![cmd.to_string()])?)) - }, + Err(()) => Err(invalid_command_message(client, cmd.to_string())), } } +fn invalid_command_message(client: &Client, user_entered_invalid_command: String) -> String { + let entity_role = client + .state() + .read_storage::() + .get(client.entity()) + .map(|admin| admin.0); + + let usable_commands = ServerChatCommand::iter() + .filter(|cmd| cmd.needs_role() <= entity_role) + .map(|cmd| cmd.keyword()) + .chain(ClientChatCommand::iter().map(|cmd| cmd.keyword())); + + let most_similar_str = usable_commands + .clone() + .min_by_key(|cmd| levenshtein(&user_entered_invalid_command, cmd)) + .expect("At least one command exists."); + + let commands_with_same_prefix = usable_commands + .filter(|cmd| cmd.starts_with(&user_entered_invalid_command) && cmd != &most_similar_str); + + format!( + "Could not find a command named {}. Did you mean any of the following? \n/{} {} \n\nType \ + /help to see a list of all commands.", + user_entered_invalid_command, + most_similar_str, + commands_with_same_prefix.fold(String::new(), |s, arg| s + "\n/" + arg) + ) +} + fn run_client_command( client: &mut Client, global_state: &mut GlobalState, @@ -174,7 +203,7 @@ fn run_client_command( ) -> Result { let command = match command { ClientChatCommand::ExperimentalShader => handle_experimental_shader, - ClientChatCommand::Help => handle_help + ClientChatCommand::Help => handle_help, ClientChatCommand::Mute => handle_mute, ClientChatCommand::Unmute => handle_unmute, }; @@ -197,11 +226,10 @@ fn handle_help( .get(client.entity()) .map(|admin| admin.0); - ClientChatCommand::iter() - .for_each(|cmd| { - message += &cmd.help_string(); - message += "\n"; - }); + ClientChatCommand::iter().for_each(|cmd| { + message += &cmd.help_string(); + message += "\n"; + }); // Iterate through all ServerChatCommands you have permission to use. ServerChatCommand::iter() .filter(|cmd| cmd.needs_role() <= entity_role) @@ -211,6 +239,7 @@ fn handle_help( }); message += "Additionally, you can use the following shortcuts:"; ServerChatCommand::iter() + .filter(|cmd| cmd.needs_role() <= entity_role) .filter_map(|cmd| cmd.short_keyword().map(|k| (k, cmd))) .for_each(|(k, cmd)| { message += &format!(" /{} => /{}", k, cmd.keyword()); @@ -219,43 +248,6 @@ fn handle_help( } } -fn handle_invalid_command( - client: &Client, - _global_state: &mut GlobalState, - args: Vec, -) -> Result { - if let Some(user_entered_invalid_command) = parse_cmd_args!(args, String) { - let entity_role = client - .state() - .read_storage::() - .get(client.entity()) - .map(|admin| admin.0); - - let usable_commands = ServerChatCommand::iter() - .filter(|cmd| cmd.needs_role() <= entity_role) - .map(|cmd| cmd.keyword()) - .chain(ClientChatCommand::iter().map(|cmd| cmd.keyword())); - - let most_similar_str = usable_commands.clone() - .sorted_by_key(|cmd| levenshtein(&user_entered_invalid_command, cmd)) - .collect::>()[0]; - - let commands_with_same_prefix = usable_commands - .filter(|cmd| { - cmd.starts_with(&user_entered_invalid_command) && cmd != &most_similar_str - }); - - return Err(format!( - "Could not find a command named {}. Did you mean any of the following? \n/{} {} \ - \n\nType /help to see a list of all commands.", - user_entered_invalid_command, - most_similar_str, - commands_with_same_prefix.fold(String::new(), |s, arg| s + "\n/" + arg) - )); - } - Err("Command failed parsing".to_string()) -} - fn handle_mute( client: &Client, global_state: &mut GlobalState,