diff --git a/common/src/cmd.rs b/common/src/cmd.rs index 2ab72f83a9..bb134a5741 100644 --- a/common/src/cmd.rs +++ b/common/src/cmd.rs @@ -1,14 +1,10 @@ use crate::{assets, comp::Player, state::State}; -use lazy_static::lazy_static; use specs::prelude::{Join, WorldExt}; - /// Struct representing a command that a user can run from server chat. -pub struct ChatCommand { - /// The keyword used to invoke the command, omitting the leading '/'. - pub keyword: &'static str, +pub struct ChatCommandData { /// A format string for parsing arguments. - pub args: Vec, + pub args: Vec, /// A one-line message that explains what the command does pub description: &'static str, /// A boolean that is used to check whether the command requires @@ -16,15 +12,9 @@ pub struct ChatCommand { pub needs_admin: bool, } -impl ChatCommand { - pub fn new( - keyword: &'static str, - args: Vec, - description: &'static str, - needs_admin: bool, - ) -> Self { +impl ChatCommandData { + pub fn new(args: Vec, description: &'static str, needs_admin: bool) -> Self { Self { - keyword, args, description, needs_admin, @@ -32,17 +22,267 @@ impl ChatCommand { } } -lazy_static! { - static ref CHAT_COMMANDS: Vec = { - use ArgumentSyntax::*; - vec![ - ChatCommand::new("help", vec![Command(true)], "Display information about commands", false), - ] - }; +// Please keep this sorted alphabetically :-) +#[derive(Copy, Clone)] +pub enum ChatCommand { + Adminify, + Alias, + Build, + Debug, + DebugColumn, + Explosion, + GiveExp, + GiveItem, + Goto, + Health, + Help, + Jump, + Kill, + KillNpcs, + Lantern, + Light, + Object, + Players, + RemoveLights, + SetLevel, + Spawn, + Sudo, + Tell, + Time, + Tp, + Version, + Waypoint, +} + +// Thank you for keeping this sorted alphabetically :-) +pub static CHAT_COMMANDS: &'static [ChatCommand] = &[ + ChatCommand::Adminify, + ChatCommand::Alias, + ChatCommand::Build, + ChatCommand::Debug, + ChatCommand::DebugColumn, + ChatCommand::Explosion, + ChatCommand::GiveExp, + ChatCommand::GiveItem, + ChatCommand::Goto, + ChatCommand::Health, + ChatCommand::Help, + ChatCommand::Jump, + ChatCommand::Kill, + ChatCommand::KillNpcs, + ChatCommand::Lantern, + ChatCommand::Light, + ChatCommand::Object, + ChatCommand::Players, + ChatCommand::RemoveLights, + ChatCommand::SetLevel, + ChatCommand::Spawn, + ChatCommand::Sudo, + ChatCommand::Tell, + ChatCommand::Time, + ChatCommand::Tp, + ChatCommand::Version, + ChatCommand::Waypoint, +]; + +impl ChatCommand { + pub fn data(&self) -> ChatCommandData { + use ArgumentSpec::*; + let cmd = ChatCommandData::new; + match self { + ChatCommand::Adminify => cmd( + vec![PlayerName(false)], + "Temporarily gives a player admin permissions or removes them", + true, + ), + ChatCommand::Alias => cmd(vec![Any("name", false)], "Change your alias", false), + ChatCommand::Build => cmd(vec![], "Toggles build mode on and off", true), + ChatCommand::Debug => cmd(vec![], "Place all debug items into your pack.", true), + ChatCommand::DebugColumn => cmd( + vec![Float("x", f32::NAN, false), Float("y", f32::NAN, false)], + "Prints some debug information about a column", + false, + ), + ChatCommand::Explosion => cmd( + vec![Float("radius", 5.0, false)], + "Explodes the ground around you", + true, + ), + ChatCommand::GiveExp => cmd( + vec![Integer("amount", 50, false)], + "Give experience to yourself", + true, + ), + ChatCommand::GiveItem => cmd( + vec![ItemSpec(false), Integer("num", 1, true)], + "Give yourself some items", + true, + ), + ChatCommand::Goto => cmd( + vec![ + Float("x", 0.0, false), + Float("y", 0.0, false), + Float("z", 0.0, false), + ], + "Teleport to a position", + true, + ), + ChatCommand::Health => cmd( + vec![Integer("hp", 100, false)], + "Set your current health", + true, + ), + ChatCommand::Help => ChatCommandData::new( + vec![Command(true)], + "Display information about commands", + false, + ), + ChatCommand::Jump => cmd( + vec![ + Float("x", 0.0, false), + Float("y", 0.0, false), + Float("z", 0.0, false), + ], + "Offset your current position", + true, + ), + ChatCommand::Kill => cmd(vec![], "Kill yourself", false), + ChatCommand::KillNpcs => cmd(vec![], "Kill the NPCs", true), + ChatCommand::Lantern => cmd( + vec![ + Float("strength", 5.0, false), + Float("r", 1.0, true), + Float("g", 1.0, true), + Float("b", 1.0, true), + ], + "Change your lantern's strength and color", + true, + ), + ChatCommand::Light => cmd( + vec![ + Float("r", 1.0, true), + Float("g", 1.0, true), + Float("b", 1.0, true), + Float("x", 0.0, true), + Float("y", 0.0, true), + Float("z", 0.0, true), + Float("strength", 5.0, true), + ], + "Spawn entity with light", + true, + ), + ChatCommand::Object => cmd(vec![/*TODO*/], "Spawn an object", true), + ChatCommand::Players => cmd(vec![], "Lists players currently online", false), + ChatCommand::RemoveLights => cmd( + vec![Float("radius", 20.0, true)], + "Removes all lights spawned by players", + true, + ), + ChatCommand::SetLevel => { + cmd(vec![Integer("level", 10, false)], "Set player Level", true) + }, + ChatCommand::Spawn => cmd(vec![/*TODO*/], "Spawn a test entity", true), + ChatCommand::Sudo => cmd( + vec![PlayerName(false), Command(false), Message /* TODO */], + "Run command as if you were another player", + true, + ), + ChatCommand::Tell => cmd( + vec![PlayerName(false), Message], + "Send a message to another player", + false, + ), + ChatCommand::Time => cmd(vec![/*TODO*/], "Set the time of day", true), + ChatCommand::Tp => cmd(vec![PlayerName(true)], "Teleport to another player", true), + ChatCommand::Version => cmd(vec![], "Prints server version", false), + ChatCommand::Waypoint => { + cmd(vec![], "Set your waypoint to your current position", true) + }, + } + } + + pub fn keyword(&self) -> &'static str { + match self { + ChatCommand::Adminify => "adminify", + ChatCommand::Alias => "alias", + ChatCommand::Build => "build", + ChatCommand::Debug => "debug", + ChatCommand::DebugColumn => "debug_column", + ChatCommand::Explosion => "explosion", + ChatCommand::GiveExp => "give_exp", + ChatCommand::GiveItem => "give_item", + ChatCommand::Goto => "goto", + ChatCommand::Health => "health", + ChatCommand::Help => "help", + ChatCommand::Jump => "jump", + ChatCommand::Kill => "kill", + ChatCommand::KillNpcs => "kill_npcs", + ChatCommand::Lantern => "lantern", + ChatCommand::Light => "light", + ChatCommand::Object => "object", + ChatCommand::Players => "players", + ChatCommand::RemoveLights => "remove_lights", + ChatCommand::SetLevel => "set_level", + ChatCommand::Spawn => "spawn", + ChatCommand::Sudo => "sudo", + ChatCommand::Tell => "tell", + ChatCommand::Time => "time", + ChatCommand::Tp => "tp", + ChatCommand::Version => "version", + ChatCommand::Waypoint => "waypoint", + } + } + + pub fn help_string(&self) -> String { + let data = self.data(); + let usage = std::iter::once(format!("/{}", self.keyword())) + .chain(data.args.iter().map(|arg| arg.usage_string())) + .collect::>() + .join(" "); + format!("{}: {}", usage, data.description) + } + + pub fn needs_admin(&self) -> bool { self.data().needs_admin } + + pub fn arg_fmt(&self) -> String { + self.data() + .args + .iter() + .map(|arg| match arg { + ArgumentSpec::PlayerName(_) => "{}", + ArgumentSpec::ItemSpec(_) => "{}", + ArgumentSpec::Float(_, _, _) => "{f}", + ArgumentSpec::Integer(_, _, _) => "{d}", + ArgumentSpec::Any(_, _) => "{}", + ArgumentSpec::Command(_) => "{}", + ArgumentSpec::Message => "{/.*/}", + ArgumentSpec::OneOf(_, _, _, _) => "{}", // TODO + }) + .collect::>() + .join(" ") + } +} + +impl std::str::FromStr for ChatCommand { + type Err = (); + + fn from_str(keyword: &str) -> Result { + let kwd = if keyword.chars().next() == Some('/') { + &keyword[1..] + } else { + &keyword[..] + }; + for c in CHAT_COMMANDS { + if kwd == c.keyword() { + return Ok(*c); + } + } + return Err(()); + } } /// Representation for chat command arguments -pub enum ArgumentSyntax { +pub enum ArgumentSpec { /// The argument refers to a player by alias PlayerName(bool), /// The argument refers to an item asset by path @@ -56,61 +296,74 @@ pub enum ArgumentSyntax { /// * label /// * default tab-completion /// * whether it's optional - Integer(&'static str, f32, bool), + Integer(&'static str, i32, bool), + /// The argument is any string that doesn't contain spaces + Any(&'static str, bool), /// The argument is a command name Command(bool), - /// This is the final argument, consuming all characters until the end of input. + /// This is the final argument, consuming all characters until the end of + /// input. Message, /// The argument is likely an enum. The associated values are /// * label /// * Predefined string completions /// * Other completion types /// * whether it's optional - OneOf(&'static str, &'static [&'static str], Vec>, bool), + OneOf( + &'static str, + &'static [&'static str], + Vec>, + bool, + ), } -impl ArgumentSyntax { - pub fn help_string(arg: &ArgumentSyntax) -> String { - match arg { - ArgumentSyntax::PlayerName(optional) => { +impl ArgumentSpec { + pub fn usage_string(&self) -> String { + match self { + ArgumentSpec::PlayerName(optional) => { if *optional { "[player]".to_string() } else { "".to_string() } }, - ArgumentSyntax::ItemSpec(optional) => { + ArgumentSpec::ItemSpec(optional) => { if *optional { "[item]".to_string() } else { "".to_string() } }, - ArgumentSyntax::Float(label, _, optional) => { + ArgumentSpec::Float(label, _, optional) => { if *optional { format!("[{}]", label) } else { format!("<{}>", label) } }, - ArgumentSyntax::Integer(label, _, optional) => { + ArgumentSpec::Integer(label, _, optional) => { if *optional { format!("[{}]", label) } else { format!("<{}>", label) } }, - ArgumentSyntax::Command(optional) => { + ArgumentSpec::Any(label, optional) => { + if *optional { + format!("[{}]", label) + } else { + format!("<{}>", label) + } + }, + ArgumentSpec::Command(optional) => { if *optional { "[[/]command]".to_string() } else { "<[/]command>".to_string() } }, - ArgumentSyntax::Message => { - "".to_string() - }, - ArgumentSyntax::OneOf(label, _, _, optional) => { + ArgumentSpec::Message => "".to_string(), + ArgumentSpec::OneOf(label, _, _, optional) => { if *optional { format! {"[{}]", label} } else { @@ -122,25 +375,26 @@ impl ArgumentSyntax { pub fn complete(&self, state: &State, part: &String) -> Vec { match self { - ArgumentSyntax::PlayerName(_) => (&state.ecs().read_storage::()) + ArgumentSpec::PlayerName(_) => (&state.ecs().read_storage::()) .join() .filter(|player| player.alias.starts_with(part)) .map(|player| player.alias.clone()) .collect(), - ArgumentSyntax::ItemSpec(_) => assets::iterate() + ArgumentSpec::ItemSpec(_) => assets::iterate() .filter(|asset| asset.starts_with(part)) .map(|c| c.to_string()) .collect(), - ArgumentSyntax::Float(_, x, _) => vec![format!("{}", x)], - ArgumentSyntax::Integer(_, x, _) => vec![format!("{}", x)], - ArgumentSyntax::Command(_) => CHAT_COMMANDS + ArgumentSpec::Float(_, x, _) => vec![format!("{}", x)], + ArgumentSpec::Integer(_, x, _) => vec![format!("{}", x)], + ArgumentSpec::Any(_, _) => vec![], + ArgumentSpec::Command(_) => CHAT_COMMANDS .iter() - .map(|com| com.keyword.clone()) + .map(|com| com.keyword()) .filter(|kwd| kwd.starts_with(part) || format!("/{}", kwd).starts_with(part)) .map(|c| c.to_string()) .collect(), - ArgumentSyntax::Message => vec![], - ArgumentSyntax::OneOf(_, strings, alts, _) => { + ArgumentSpec::Message => vec![], + ArgumentSpec::OneOf(_, strings, alts, _) => { let string_completions = strings .iter() .filter(|string| string.starts_with(part)) @@ -150,7 +404,7 @@ impl ArgumentSyntax { .flat_map(|b| (*b).complete(&state, part)) .map(|c| c.to_string()); string_completions.chain(alt_completions).collect() - } + }, } } } diff --git a/server/src/cmd.rs b/server/src/cmd.rs index 9b63633afc..44a64db825 100644 --- a/server/src/cmd.rs +++ b/server/src/cmd.rs @@ -5,7 +5,9 @@ use crate::{Server, StateExt}; use chrono::{NaiveTime, Timelike}; use common::{ - assets, comp, + assets, + cmd::{ChatCommand, CHAT_COMMANDS}, + comp, event::{EventBus, ServerEvent}, msg::{PlayerListUpdate, ServerMsg}, npc::{self, get_npc_name}, @@ -20,274 +22,70 @@ use specs::{Builder, Entity as EcsEntity, Join, WorldExt}; use vek::*; use world::util::Sampler; -use lazy_static::lazy_static; use log::error; use scan_fmt::{scan_fmt, scan_fmt_some}; -/// Struct representing a command that a user can run from server chat. -pub struct ChatCommand { - /// The keyword used to invoke the command, omitting the leading '/'. - pub keyword: &'static str, - /// A format string for parsing arguments. - arg_fmt: &'static str, - /// A message that explains how the command is used. - help_string: &'static str, - /// A boolean that is used to check whether the command requires - /// administrator permissions or not. - needs_admin: bool, - /// Handler function called when the command is executed. - /// # Arguments - /// * `&mut Server` - the `Server` instance executing the command. - /// * `EcsEntity` - an `Entity` corresponding to the player that invoked the - /// command. - /// * `EcsEntity` - an `Entity` for the player on whom the command is - /// invoked. This differs from the previous argument when using /sudo - /// * `String` - a `String` containing the part of the command after the - /// keyword. - /// * `&ChatCommand` - the command to execute with the above arguments. - /// Handler functions must parse arguments from the the given `String` - /// (`scan_fmt!` is included for this purpose). - handler: fn(&mut Server, EcsEntity, EcsEntity, String, &ChatCommand), +pub trait ChatCommandExt { + fn execute(&self, server: &mut Server, entity: EcsEntity, args: String); } - -impl ChatCommand { - /// Creates a new chat command. - pub fn new( - keyword: &'static str, - arg_fmt: &'static str, - help_string: &'static str, - needs_admin: bool, - handler: fn(&mut Server, EcsEntity, EcsEntity, String, &ChatCommand), - ) -> Self { - Self { - keyword, - arg_fmt, - help_string, - needs_admin, - handler, - } - } - - /// Calls the contained handler function, passing `&self` as the last - /// argument. - pub fn execute(&self, server: &mut Server, entity: EcsEntity, args: String) { - if self.needs_admin { - if !server.entity_is_admin(entity) { - server.notify_client( - entity, - ServerMsg::private(format!( - "You don't have permission to use '/{}'.", - self.keyword - )), - ); - return; - } else { - (self.handler)(server, entity, entity, args, self); - } +impl ChatCommandExt for ChatCommand { + fn execute(&self, server: &mut Server, entity: EcsEntity, args: String) { + let cmd_data = self.data(); + if cmd_data.needs_admin && !server.entity_is_admin(entity) { + server.notify_client( + entity, + ServerMsg::private(format!( + "You don't have permission to use '/{}'.", + self.keyword() + )), + ); + return; } else { - (self.handler)(server, entity, entity, args, self); + get_handler(self)(server, entity, entity, args, &self); } } } -lazy_static! { - /// Static list of chat commands available to the server. - pub static ref CHAT_COMMANDS: Vec = vec![ - ChatCommand::new( - "give_item", - "{} {d}", - "/give_item [num]\n\ - Example items: common/items/apple, common/items/debug/boost", - true, - handle_give,), - ChatCommand::new( - "jump", - "{d} {d} {d}", - "/jump : Offset your current position", - true, - handle_jump, - ), - ChatCommand::new( - "goto", - "{d} {d} {d}", - "/goto : Teleport to a position", - true, - handle_goto, - ), - ChatCommand::new( - "alias", - "{}", - "/alias : Change your alias", - false, - handle_alias, - ), - ChatCommand::new( - "tp", - "{}", - "/tp : Teleport to another player", - true, - handle_tp, - ), - ChatCommand::new( - "kill", - "{}", - "/kill : Kill yourself", - false, - handle_kill, - ), - ChatCommand::new( - "time", - "{} {s}", - "/time or [Time of day] : Set the time of day", - true, - handle_time, - ), - ChatCommand::new( - "spawn", - "{} {} {d}", - "/spawn [amount] : Spawn a test entity", - true, - handle_spawn, - ), - ChatCommand::new( - "players", - "{}", - "/players : Lists players currently online", - false, - handle_players, - ), - ChatCommand::new( - "help", "", "/help: Display this message", false, handle_help), - ChatCommand::new( - "health", - "{}", - "/health : Set your current health", - true, - handle_health, - ), - ChatCommand::new( - "build", - "", - "/build : Toggles build mode on and off", - true, - handle_build, - ), - ChatCommand::new( - "tell", - "{}", - "/tell : Send a message to another player", - false, - handle_tell, - ), - ChatCommand::new( - "killnpcs", - "{}", - "/killnpcs : Kill the NPCs", - true, - handle_killnpcs, - ), - ChatCommand::new( - "object", - "{}", - "/object [Name]: Spawn an object", - true, - handle_object, - ), - ChatCommand::new( - "light", - "{} {} {} {} {} {} {}", - "/light > < > <>>: Spawn entity with light", - true, - handle_light, - ), - ChatCommand::new( - "lantern", - "{} {} {} {}", - "/lantern [ ]: Change your lantern's strength and color", - true, - handle_lantern, - ), - ChatCommand::new( - "explosion", - "{}", - "/explosion : Explodes the ground around you", - true, - handle_explosion, - ), - ChatCommand::new( - "waypoint", - "{}", - "/waypoint : Set your waypoint to your current position", - true, - handle_waypoint, - ), - ChatCommand::new( - "adminify", - "{}", - "/adminify : Temporarily gives a player admin permissions or removes them", - true, - handle_adminify, - ), - ChatCommand::new( - "debug_column", - "{} {}", - "/debug_column : Prints some debug information about a column", - false, - handle_debug_column, - ), - ChatCommand::new( - "give_exp", - "{d} {}", - "/give_exp : Give experience to yourself or specify a target player", - true, - handle_exp, - ), - ChatCommand::new( - "set_level", - "{d} {}", - "/set_level : Set own Level or specify a target player", - true, - handle_level - ), - ChatCommand::new( - "removelights", - "{}", - "/removelights [radius] : Removes all lights spawned by players", - true, - handle_remove_lights, - ), - ChatCommand::new( - "debug", - "", - "/debug : Place all debug items into your pack.", - true, - handle_debug, - ), - ChatCommand::new( - "sudo", - "{} {} {/.*/}", - "/sudo / [args...] : Run command as if you were another player", - true, - handle_sudo, - ), - ChatCommand::new( - "version", - "", - "/version : Prints server version", - false, - handle_version, - ), - ]; +type CommandHandler = fn(&mut Server, EcsEntity, EcsEntity, String, &ChatCommand); +fn get_handler(cmd: &ChatCommand) -> CommandHandler { + match cmd { + ChatCommand::Adminify => handle_adminify, + ChatCommand::Alias => handle_alias, + ChatCommand::Build => handle_build, + ChatCommand::Debug => handle_debug, + ChatCommand::DebugColumn => handle_debug_column, + ChatCommand::Explosion => handle_explosion, + ChatCommand::GiveExp => handle_give_exp, + ChatCommand::GiveItem => handle_give_item, + ChatCommand::Goto => handle_goto, + ChatCommand::Health => handle_health, + ChatCommand::Help => handle_help, + ChatCommand::Jump => handle_jump, + ChatCommand::Kill => handle_kill, + ChatCommand::KillNpcs => handle_kill_npcs, + ChatCommand::Lantern => handle_lantern, + ChatCommand::Light => handle_light, + ChatCommand::Object => handle_object, + ChatCommand::Players => handle_players, + ChatCommand::RemoveLights => handle_remove_lights, + ChatCommand::SetLevel => handle_set_level, + ChatCommand::Spawn => handle_spawn, + ChatCommand::Sudo => handle_sudo, + ChatCommand::Tell => handle_tell, + ChatCommand::Time => handle_time, + ChatCommand::Tp => handle_tp, + ChatCommand::Version => handle_version, + ChatCommand::Waypoint => handle_waypoint, + } } - -fn handle_give( +fn handle_give_item( server: &mut Server, client: EcsEntity, target: EcsEntity, args: String, action: &ChatCommand, ) { - if let (Some(item_name), give_amount_opt) = scan_fmt_some!(&args, action.arg_fmt, String, u32) { + if let (Some(item_name), give_amount_opt) = scan_fmt_some!(&args, &action.arg_fmt(), String, u32) { let give_amount = give_amount_opt.unwrap_or(1); if let Ok(item) = assets::load_cloned(&item_name) { let mut item: Item = item; @@ -346,7 +144,7 @@ fn handle_give( ); } } else { - server.notify_client(client, ServerMsg::private(String::from(action.help_string))); + server.notify_client(client, ServerMsg::private(String::from(action.help_string()))); } } @@ -357,7 +155,7 @@ fn handle_jump( args: String, action: &ChatCommand, ) { - if let Ok((x, y, z)) = scan_fmt!(&args, action.arg_fmt, f32, f32, f32) { + if let Ok((x, y, z)) = scan_fmt!(&args, &action.arg_fmt(), f32, f32, f32) { match server.state.read_component_cloned::(target) { Some(current_pos) => { server @@ -380,7 +178,7 @@ fn handle_goto( args: String, action: &ChatCommand, ) { - if let Ok((x, y, z)) = scan_fmt!(&args, action.arg_fmt, f32, f32, f32) { + if let Ok((x, y, z)) = scan_fmt!(&args, &action.arg_fmt(), f32, f32, f32) { if server .state .read_component_cloned::(target) @@ -397,7 +195,10 @@ fn handle_goto( ); } } else { - server.notify_client(client, ServerMsg::private(String::from(action.help_string))); + server.notify_client( + client, + ServerMsg::private(String::from(action.help_string())), + ); } } @@ -432,7 +233,7 @@ fn handle_time( args: String, action: &ChatCommand, ) { - let time = scan_fmt_some!(&args, action.arg_fmt, String); + let time = scan_fmt_some!(&args, &action.arg_fmt(), String); let new_time = match time.as_ref().map(|s| s.as_str()) { Some("midnight") => NaiveTime::from_hms(0, 0, 0), Some("night") => NaiveTime::from_hms(20, 0, 0), @@ -490,7 +291,7 @@ fn handle_health( args: String, action: &ChatCommand, ) { - if let Ok(hp) = scan_fmt!(&args, action.arg_fmt, u32) { + if let Ok(hp) = scan_fmt!(&args, &action.arg_fmt(), u32) { if let Some(stats) = server .state .ecs() @@ -519,7 +320,7 @@ fn handle_alias( args: String, action: &ChatCommand, ) { - if let Ok(alias) = scan_fmt!(&args, action.arg_fmt, String) { + if let Ok(alias) = scan_fmt!(&args, &action.arg_fmt(), String) { server .state .ecs_mut() @@ -540,7 +341,10 @@ fn handle_alias( server.state.notify_registered_clients(msg); } } else { - server.notify_client(client, ServerMsg::private(String::from(action.help_string))); + server.notify_client( + client, + ServerMsg::private(String::from(action.help_string())), + ); } } @@ -551,7 +355,7 @@ fn handle_tp( args: String, action: &ChatCommand, ) { - if let Ok(alias) = scan_fmt!(&args, action.arg_fmt, String) { + if let Ok(alias) = scan_fmt!(&args, &action.arg_fmt(), String) { let ecs = server.state.ecs(); let opt_player = (&ecs.entities(), &ecs.read_storage::()) .join() @@ -576,7 +380,7 @@ fn handle_tp( ); server.notify_client( client, - ServerMsg::private(String::from(action.help_string)), + ServerMsg::private(String::from(action.help_string())), ); }, }, @@ -585,7 +389,10 @@ fn handle_tp( }, } } else { - server.notify_client(client, ServerMsg::private(String::from(action.help_string))); + server.notify_client( + client, + ServerMsg::private(String::from(action.help_string())), + ); } } @@ -596,7 +403,7 @@ fn handle_spawn( args: String, action: &ChatCommand, ) { - match scan_fmt_some!(&args, action.arg_fmt, String, npc::NpcBody, String) { + match scan_fmt_some!(&args, &action.arg_fmt(), String, npc::NpcBody, String) { (Some(opt_align), Some(npc::NpcBody(id, mut body)), opt_amount) => { if let Some(alignment) = parse_alignment(target, &opt_align) { let amount = opt_amount @@ -659,7 +466,10 @@ fn handle_spawn( } }, _ => { - server.notify_client(client, ServerMsg::private(String::from(action.help_string))); + server.notify_client( + client, + ServerMsg::private(String::from(action.help_string())), + ); }, } } @@ -733,12 +543,16 @@ fn handle_help( server: &mut Server, client: EcsEntity, _target: EcsEntity, - _args: String, - _action: &ChatCommand, + args: String, + action: &ChatCommand, ) { - for cmd in CHAT_COMMANDS.iter() { - if !cmd.needs_admin || server.entity_is_admin(client) { - server.notify_client(client, ServerMsg::private(String::from(cmd.help_string))); + if let Some(cmd) = scan_fmt_some!(&args, &action.arg_fmt(), ChatCommand) { + server.notify_client(client, ServerMsg::private(String::from(cmd.help_string()))); + } else { + for cmd in CHAT_COMMANDS.iter() { + if !cmd.needs_admin() || server.entity_is_admin(client) { + server.notify_client(client, ServerMsg::private(String::from(cmd.help_string()))); + } } } } @@ -753,7 +567,7 @@ fn parse_alignment(owner: EcsEntity, alignment: &str) -> Option } } -fn handle_killnpcs( +fn handle_kill_npcs( server: &mut Server, client: EcsEntity, _target: EcsEntity, @@ -781,9 +595,9 @@ fn handle_object( client: EcsEntity, target: EcsEntity, args: String, - _action: &ChatCommand, + action: &ChatCommand, ) { - let obj_type = scan_fmt!(&args, _action.arg_fmt, String); + let obj_type = scan_fmt!(&args, &action.arg_fmt(), String); let pos = server .state @@ -894,7 +708,7 @@ fn handle_light( action: &ChatCommand, ) { let (opt_r, opt_g, opt_b, opt_x, opt_y, opt_z, opt_s) = - scan_fmt_some!(&args, action.arg_fmt, f32, f32, f32, f32, f32, f32, f32); + scan_fmt_some!(&args, &action.arg_fmt(), f32, f32, f32, f32, f32, f32, f32); let mut light_emitter = comp::LightEmitter::default(); let mut light_offset_opt = None; @@ -955,7 +769,8 @@ fn handle_lantern( args: String, action: &ChatCommand, ) { - if let (Some(s), r, g, b) = scan_fmt_some!(&args, action.arg_fmt, f32, f32, f32, f32) { + println!("args: '{}', fmt: '{}'", &args, &action.arg_fmt()); + if let (Some(s), r, g, b) = scan_fmt_some!(&args, &action.arg_fmt(), f32, f32, f32, f32) { if let Some(light) = server .state .ecs() @@ -987,7 +802,10 @@ fn handle_lantern( ); } } else { - server.notify_client(client, ServerMsg::private(String::from(action.help_string))); + server.notify_client( + client, + ServerMsg::private(String::from(action.help_string())), + ); } } @@ -998,7 +816,7 @@ fn handle_explosion( args: String, action: &ChatCommand, ) { - let power = scan_fmt!(&args, action.arg_fmt, f32).unwrap_or(8.0); + let power = scan_fmt!(&args, &action.arg_fmt(), f32).unwrap_or(8.0); if power > 512.0 { server.notify_client( @@ -1062,7 +880,7 @@ fn handle_adminify( args: String, action: &ChatCommand, ) { - if let Ok(alias) = scan_fmt!(&args, action.arg_fmt, String) { + if let Ok(alias) = scan_fmt!(&args, &action.arg_fmt(), String) { let ecs = server.state.ecs(); let opt_player = (&ecs.entities(), &ecs.read_storage::()) .join() @@ -1082,11 +900,17 @@ fn handle_adminify( client, ServerMsg::private(format!("Player '{}' not found!", alias)), ); - server.notify_client(client, ServerMsg::private(String::from(action.help_string))); + server.notify_client( + client, + ServerMsg::private(String::from(action.help_string())), + ); }, } } else { - server.notify_client(client, ServerMsg::private(String::from(action.help_string))); + server.notify_client( + client, + ServerMsg::private(String::from(action.help_string())), + ); } } @@ -1104,7 +928,7 @@ fn handle_tell( ); return; } - if let Ok(alias) = scan_fmt!(&args, action.arg_fmt, String) { + if let Ok(alias) = scan_fmt!(&args, &action.arg_fmt(), String) { let ecs = server.state.ecs(); let msg = &args[alias.len()..args.len()]; if let Some(player) = (&ecs.entities(), &ecs.read_storage::()) @@ -1152,7 +976,10 @@ fn handle_tell( ); } } else { - server.notify_client(client, ServerMsg::private(String::from(action.help_string))); + server.notify_client( + client, + ServerMsg::private(String::from(action.help_string())), + ); } } @@ -1180,7 +1007,7 @@ fn handle_debug_column( ) { let sim = server.world.sim(); let sampler = server.world.sample_columns(); - if let Ok((x, y)) = scan_fmt!(&args, action.arg_fmt, i32, i32) { + if let Ok((x, y)) = scan_fmt!(&args, &action.arg_fmt(), i32, i32) { let wpos = Vec2::new(x, y); /* let chunk_pos = wpos.map2(TerrainChunkSize::RECT_SIZE, |e, sz: u32| { e / sz as i32 @@ -1244,7 +1071,10 @@ spawn_rate {:?} "#, ); } } else { - server.notify_client(client, ServerMsg::private(String::from(action.help_string))); + server.notify_client( + client, + ServerMsg::private(String::from(action.help_string())), + ); } } @@ -1264,14 +1094,14 @@ fn find_target( } } -fn handle_exp( +fn handle_give_exp( server: &mut Server, client: EcsEntity, target: EcsEntity, args: String, action: &ChatCommand, ) { - let (a_exp, a_alias) = scan_fmt_some!(&args, action.arg_fmt, i64, String); + let (a_exp, a_alias) = scan_fmt_some!(&args, &action.arg_fmt(), i64, String); if let Some(exp) = a_exp { let ecs = server.state.ecs_mut(); @@ -1298,14 +1128,14 @@ fn handle_exp( } } -fn handle_level( +fn handle_set_level( server: &mut Server, client: EcsEntity, target: EcsEntity, args: String, action: &ChatCommand, ) { - let (a_lvl, a_alias) = scan_fmt_some!(&args, action.arg_fmt, u32, String); + 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(); @@ -1378,7 +1208,7 @@ fn handle_remove_lights( args: String, action: &ChatCommand, ) { - let opt_radius = scan_fmt_some!(&args, action.arg_fmt, f32); + let opt_radius = scan_fmt_some!(&args, &action.arg_fmt(), f32); let opt_player_pos = server.state.read_component_cloned::(target); let mut to_delete = vec![]; @@ -1430,7 +1260,7 @@ fn handle_sudo( action: &ChatCommand, ) { if let (Some(player_alias), Some(cmd), cmd_args) = - scan_fmt_some!(&args, action.arg_fmt, String, String, String) + scan_fmt_some!(&args, &action.arg_fmt(), String, String, String) { let cmd_args = cmd_args.unwrap_or(String::from("")); let cmd = if cmd.chars().next() == Some('/') { @@ -1438,14 +1268,14 @@ fn handle_sudo( } else { cmd }; - if let Some(action) = CHAT_COMMANDS.iter().find(|c| c.keyword == cmd) { + if let Some(action) = CHAT_COMMANDS.iter().find(|c| c.keyword() == cmd) { let ecs = server.state.ecs(); let entity_opt = (&ecs.entities(), &ecs.read_storage::()) .join() .find(|(_, player)| player.alias == player_alias) .map(|(entity, _)| entity); if let Some(entity) = entity_opt { - (action.handler)(server, client, entity, cmd_args, action); + get_handler(action)(server, client, entity, cmd_args, action); } else { server.notify_client( client, @@ -1459,7 +1289,10 @@ fn handle_sudo( ); } } else { - server.notify_client(client, ServerMsg::private(String::from(action.help_string))); + server.notify_client( + client, + ServerMsg::private(String::from(action.help_string())), + ); } } @@ -1479,3 +1312,4 @@ fn handle_version( )), ); } + diff --git a/server/src/lib.rs b/server/src/lib.rs index 97126cb648..eb29844b80 100644 --- a/server/src/lib.rs +++ b/server/src/lib.rs @@ -22,11 +22,12 @@ use crate::{ auth_provider::AuthProvider, chunk_generator::ChunkGenerator, client::{Client, RegionSubscription}, - cmd::CHAT_COMMANDS, + cmd::ChatCommandExt, state_ext::StateExt, sys::sentinel::{DeletedEntities, TrackedComps}, }; use common::{ + cmd::ChatCommand, comp, event::{EventBus, ServerEvent}, msg::{ClientMsg, ClientState, ServerInfo, ServerMsg}, @@ -534,18 +535,16 @@ impl Server { }; // Find the command object and run its handler. - let action_opt = CHAT_COMMANDS.iter().find(|x| x.keyword == kwd); - match action_opt { - Some(action) => action.execute(self, entity, args), - // Unknown command - None => { - if let Some(client) = self.state.ecs().write_storage::().get_mut(entity) { - client.notify(ServerMsg::private(format!( - "Unknown command '/{}'.\nType '/help' for available commands", - kwd - ))); - } - }, + if let Ok(command) = kwd.parse::() { + command.execute(self, entity, args); + } else { + self.notify_client( + entity, + ServerMsg::private(format!( + "Unknown command '/{}'.\nType '/help' for available commands", + kwd + )), + ); } }